/*
 * Cobertura - http://cobertura.sourceforge.net/
 *
 * Copyright (C) 2005 Jeremy Thomerson
 * Copyright (C) 2005 Grzegorz Lukasik
 * Copyright (C) 2009 Charlie Squires
 * Copyright (C) 2009 John Lewis
 *
 * Cobertura is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * Cobertura is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cobertura; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */
package net.sourceforge.cobertura.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Maps source file names to existing files. After adding description of places files can be found in, it can be used to localize the files.

FileFinder supports two types of source files locations:
  • source root directory, defines the directory under which source files are located,
  • pair (base directory, file path relative to base directory).
The difference between these two is that in case of the first you add all source files under the specified root directory, and in the second you add exactly one file. In both cases file to be found has to be located under subdirectory that maps to package definition provided with the source file name.
Author:Jeremy Thomerson
/** * Maps source file names to existing files. After adding description * of places files can be found in, it can be used to localize * the files. * <p/> * <p/> * FileFinder supports two types of source files locations: * <ul> * <li>source root directory, defines the directory under * which source files are located,</li> * <li>pair (base directory, file path relative to base directory).</li> * </ul> * The difference between these two is that in case of the first you add all * source files under the specified root directory, and in the second you add * exactly one file. In both cases file to be found has to be located under * subdirectory that maps to package definition provided with the source file name. * * @author Jeremy Thomerson */
public class FileFinder { private static Logger LOGGER = LoggerFactory.getLogger(FileFinder.class); // Contains Strings with directory paths private Set sourceDirectories = new HashSet(); // Contains pairs (String directoryRoot, Set fileNamesRelativeToRoot) private Map sourceFilesMap = new HashMap();
Adds directory that is a root of sources. A source file that is under this directory will be found if relative path to the file from root matches package name.

Example:

fileFinder.addSourceDirectory( "C:/MyProject/src/main");
fileFinder.addSourceDirectory( "C:/MyProject/src/test");
In path both / and \ can be used.

Params:
  • directory – The root of source files
Throws:
/** * Adds directory that is a root of sources. A source file * that is under this directory will be found if relative * path to the file from root matches package name. * <p> * Example: * <pre> * fileFinder.addSourceDirectory( "C:/MyProject/src/main"); * fileFinder.addSourceDirectory( "C:/MyProject/src/test"); * </pre> * In path both / and \ can be used. * </p> * * @param directory The root of source files * * @throws NullPointerException if <code>directory</code> is <code>null</code> */
public void addSourceDirectory(String directory) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Adding sourceDirectory=[" + directory + "]"); // Change \ to / in case of Windows users directory = getCorrectedPath(directory); sourceDirectories.add(directory); }
Adds file by specifying root directory and relative path to the file in it. Adds exactly one file, relative path should match package that the source file is in, otherwise it will be not found later.

Example:

fileFinder.addSourceFile( "C:/MyProject/src/main", "com/app/MyClass.java");
fileFinder.addSourceFile( "C:/MyProject/src/test", "com/app/MyClassTest.java");
In paths both / and \ can be used.

Params:
  • baseDir – sources root directory
  • file – path to source file relative to baseDir
Throws:
/** * Adds file by specifying root directory and relative path to the * file in it. Adds exactly one file, relative path should match * package that the source file is in, otherwise it will be not * found later. * <p> * Example: * <pre> * fileFinder.addSourceFile( "C:/MyProject/src/main", "com/app/MyClass.java"); * fileFinder.addSourceFile( "C:/MyProject/src/test", "com/app/MyClassTest.java"); * </pre> * In paths both / and \ can be used. * </p> * * @param baseDir sources root directory * @param file path to source file relative to <code>baseDir</code> * * @throws NullPointerException if either <code>baseDir</code> or <code>file</code> is <code>null</code> */
public void addSourceFile(String baseDir, String file) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Adding sourceFile baseDir=[" + baseDir + "] file=[" + file + "]"); if (baseDir == null || file == null) throw new NullPointerException(); // Change \ to / in case of Windows users file = getCorrectedPath(file); baseDir = getCorrectedPath(baseDir); // Add file to sourceFilesMap Set container = (Set) sourceFilesMap.get(baseDir); if (container == null) { container = new HashSet(); sourceFilesMap.put(baseDir, container); } container.add(file); }
Maps source file name to existing file. When mapping file name first values that were added with addSourceDirectory and later added with addSourceFile are checked.
Params:
  • fileName – source file to be mapped
Throws:
Returns:existing file that maps to passed sourceFile
/** * Maps source file name to existing file. * When mapping file name first values that were added with * {@link #addSourceDirectory} and later added with {@link #addSourceFile} are checked. * * @param fileName source file to be mapped * * @return existing file that maps to passed sourceFile * * @throws IOException if cannot map source file to existing file * @throws NullPointerException if fileName is null */
public File getFileForSource(String fileName) throws IOException { // Correct file name if (LOGGER.isDebugEnabled()) LOGGER.debug("Searching for file, name=[" + fileName + "]"); fileName = getCorrectedPath(fileName); // Check inside sourceDirectories for (Iterator it = sourceDirectories.iterator(); it.hasNext();) { String directory = (String) it.next(); File file = new File(directory, fileName); if (file.isFile()) { LOGGER.debug("Found inside sourceDirectories"); return file; } } // Check inside sourceFilesMap for (Iterator it = sourceFilesMap.keySet().iterator(); it.hasNext();) { String directory = (String) it.next(); Set container = (Set) sourceFilesMap.get(directory); if (!container.contains(fileName)) continue; File file = new File(directory, fileName); if (file.isFile()) { LOGGER.debug("Found inside sourceFilesMap"); return file; } } // Have not found? Throw an error. LOGGER.debug("File not found"); throw new IOException("Cannot find source file, name=[" + fileName + "]"); }
Maps source file name to existing file or source archive. When mapping file name first values that were added with addSourceDirectory and later added with addSourceFile are checked.
Params:
  • fileName – source file to be mapped
Throws:
Returns:Source that maps to passed sourceFile or null if it can't be found
/** * Maps source file name to existing file or source archive. * When mapping file name first values that were added with * {@link #addSourceDirectory} and later added with {@link #addSourceFile} are checked. * * @param fileName source file to be mapped * * @return Source that maps to passed sourceFile or null if it can't be found * * @throws NullPointerException if fileName is null */
public Source getSource(String fileName) { File file = null; try { file = getFileForSource(fileName); return new Source(new FileInputStream(file), file); } catch (IOException e) { //Source file wasn't found. Try searching archives. return searchJarsForSource(fileName); } }
Gets a BufferedReader for a file within a jar.
Params:
  • fileName – source file to get an input stream for
Returns:Source for existing file inside a jar that maps to passed sourceFile or null if cannot map source file to existing file
/** * Gets a BufferedReader for a file within a jar. * * @param fileName source file to get an input stream for * * @return Source for existing file inside a jar that maps to passed sourceFile * or null if cannot map source file to existing file */
private Source searchJarsForSource(String fileName) { //Check inside jars in sourceDirectories for (Iterator it = sourceDirectories.iterator(); it.hasNext();) { String directory = (String) it.next(); File file = new File(directory); //Get a list of jars and zips in the directory String[] jars = file.list(new JarZipFilter()); if (jars != null) { for (String jar : jars) { try { LOGGER.debug("Looking for: " + fileName + " in " + jar); JarFile jf = new JarFile(directory + "/" + jar); //Get a list of files in the jar Enumeration<JarEntry> files = jf.entries(); //See if the jar has the class we need while (files.hasMoreElements()) { JarEntry entry = files.nextElement(); if (entry.getName().equals(fileName)) { return new Source(jf.getInputStream(entry), jf); } } } catch (Throwable t) { LOGGER.warn("Error while reading " + jar, t); } } } } return null; }
Returns a list with string for all source directories. Example: [C:/MyProject/src/main,C:/MyProject/src/test]
Returns:list with Strings for all source roots, or empty list if no source roots were specified
/** * Returns a list with string for all source directories. * Example: <code>[C:/MyProject/src/main,C:/MyProject/src/test]</code> * * @return list with Strings for all source roots, or empty list if no source roots were specified */
public List getSourceDirectoryList() { // Get names from sourceDirectories List result = new ArrayList(); for (Iterator it = sourceDirectories.iterator(); it.hasNext();) { result.add(it.next()); } // Get names from sourceFilesMap for (Iterator it = sourceFilesMap.keySet().iterator(); it.hasNext();) { result.add(it.next()); } // Return combined names return result; } private String getCorrectedPath(String path) { return path.replace('\\', '/'); }
Returns string representation of FileFinder.
/** * Returns string representation of FileFinder. */
public String toString() { return "FileFinder, source directories: " + getSourceDirectoryList().toString(); }
A filter that accepts files that end in .jar or .zip
/** * A filter that accepts files that end in .jar or .zip */
private class JarZipFilter implements FilenameFilter { public boolean accept(File dir, String name) { return (name.endsWith(".jar") || name.endsWith(".zip")); } } }