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    }