001 /*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2005 Jeremy Thomerson
005 * Copyright (C) 2005 Grzegorz Lukasik
006 *
007 * Cobertura is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published
009 * by the Free Software Foundation; either version 2 of the License,
010 * or (at your option) any later version.
011 *
012 * Cobertura is distributed in the hope that it will be useful, but
013 * WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with Cobertura; if not, write to the Free Software
019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
020 * USA
021 */
022 package net.sourceforge.cobertura.util;
023
024 import java.io.File;
025 import java.io.IOException;
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.HashSet;
029 import java.util.Iterator;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Set;
033
034 import org.apache.log4j.Logger;
035
036
037 /**
038 * Maps source file names to existing files. After adding description
039 * of places files can be found in, it can be used to localize
040 * the files.
041 *
042 * <p>
043 * FileFinder supports two types of source files locations:
044 * <ul>
045 * <li>source root directory, defines the directory under
046 * which source files are located,</li>
047 * <li>pair (base directory, file path relative to base directory).</li>
048 * </ul>
049 * The difference between these two is that in case of the first you add all
050 * source files under the specified root directory, and in the second you add
051 * exactly one file. In both cases file to be found has to be located under
052 * subdirectory that maps to package definition provided with the source file name.
053 *
054 * @author Jeremy Thomerson
055 */
056 public class FileFinder {
057
058 private static Logger LOGGER = Logger.getLogger(FileFinder.class);
059
060 // Contains Strings with directory paths
061 private Set sourceDirectories = new HashSet();
062
063 // Contains pairs (String directoryRoot, Set fileNamesRelativeToRoot)
064 private Map sourceFilesMap = new HashMap();
065
066 /**
067 * Adds directory that is a root of sources. A source file
068 * that is under this directory will be found if relative
069 * path to the file from root matches package name.
070 * <p>
071 * Example:
072 * <pre>
073 * fileFinder.addSourceDirectory( "C:/MyProject/src/main");
074 * fileFinder.addSourceDirectory( "C:/MyProject/src/test");
075 * </pre>
076 * In path both / and \ can be used.
077 * </p>
078 *
079 * @param directory The root of source files
080 * @throws NullPointerException if <code>directory</code> is <code>null</code>
081 */
082 public void addSourceDirectory( String directory) {
083 if( LOGGER.isDebugEnabled())
084 LOGGER.debug( "Adding sourceDirectory=[" + directory + "]");
085
086 // Change \ to / in case of Windows users
087 directory = getCorrectedPath(directory);
088 sourceDirectories.add(directory);
089 }
090
091 /**
092 * Adds file by specifying root directory and relative path to the
093 * file in it. Adds exactly one file, relative path should match
094 * package that the source file is in, otherwise it will be not
095 * found later.
096 * <p>
097 * Example:
098 * <pre>
099 * fileFinder.addSourceFile( "C:/MyProject/src/main", "com/app/MyClass.java");
100 * fileFinder.addSourceFile( "C:/MyProject/src/test", "com/app/MyClassTest.java");
101 * </pre>
102 * In paths both / and \ can be used.
103 * </p>
104 *
105 * @param baseDir sources root directory
106 * @param file path to source file relative to <code>baseDir</code>
107 * @throws NullPointerException if either <code>baseDir</code> or <code>file</code> is <code>null</code>
108 */
109 public void addSourceFile( String baseDir, String file) {
110 if( LOGGER.isDebugEnabled())
111 LOGGER.debug( "Adding sourceFile baseDir=[" + baseDir + "] file=[" + file + "]");
112
113 if( baseDir==null || file==null)
114 throw new NullPointerException();
115
116 // Change \ to / in case of Windows users
117 file = getCorrectedPath( file);
118 baseDir = getCorrectedPath( baseDir);
119
120 // Add file to sourceFilesMap
121 Set container = (Set) sourceFilesMap.get(baseDir);
122 if( container==null) {
123 container = new HashSet();
124 sourceFilesMap.put( baseDir, container);
125 }
126 container.add( file);
127 }
128
129 /**
130 * Maps source file name to existing file.
131 * When mapping file name first values that were added with
132 * {@link #addSourceDirectory} and later added with {@link #addSourceFile} are checked.
133 *
134 * @param fileName source file to be mapped
135 * @return existing file that maps to passed sourceFile
136 * @throws IOException if cannot map source file to existing file
137 * @throws NullPointerException if fileName is null
138 */
139 public File getFileForSource(String fileName) throws IOException {
140 // Correct file name
141 if( LOGGER.isDebugEnabled())
142 LOGGER.debug( "Searching for file, name=[" + fileName + "]");
143 fileName = getCorrectedPath( fileName);
144
145 // Check inside sourceDirectories
146 for( Iterator it=sourceDirectories.iterator(); it.hasNext();) {
147 String directory = (String)it.next();
148 File file = new File( directory, fileName);
149 if( file.isFile()) {
150 LOGGER.debug( "Found inside sourceDirectories");
151 return file;
152 }
153 }
154
155 // Check inside sourceFilesMap
156 for( Iterator it=sourceFilesMap.keySet().iterator(); it.hasNext();) {
157 String directory = (String)it.next();
158 Set container = (Set) sourceFilesMap.get(directory);
159 if( !container.contains( fileName))
160 continue;
161 File file = new File( directory, fileName);
162 if( file.isFile()) {
163 LOGGER.debug( "Found inside sourceFilesMap");
164 return file;
165 }
166 }
167
168 // Have not found? Throw an error.
169 LOGGER.debug( "File not found");
170 throw new IOException( "Cannot find source file, name=["+fileName+"]");
171 }
172
173 /**
174 * Returns a list with string for all source directories.
175 * Example: <code>[C:/MyProject/src/main,C:/MyProject/src/test]</code>
176 *
177 * @return list with Strings for all source roots, or empty list if no source roots were specified
178 */
179 public List getSourceDirectoryList() {
180 // Get names from sourceDirectories
181 List result = new ArrayList();
182 for( Iterator it=sourceDirectories.iterator(); it.hasNext();) {
183 result.add( it.next());
184 }
185
186 // Get names from sourceFilesMap
187 for( Iterator it=sourceFilesMap.keySet().iterator(); it.hasNext();) {
188 result.add(it.next());
189 }
190
191 // Return combined names
192 return result;
193 }
194
195 private String getCorrectedPath(String path) {
196 return path.replace('\\', '/');
197 }
198
199 /**
200 * Returns string representation of FileFinder.
201 */
202 public String toString() {
203 return "FileFinder, source directories: " + getSourceDirectoryList().toString();
204 }
205 }