1 /** 2 * Redistribution and use in source and binary forms, with or without 3 * modification, are permitted provided that the following conditions are 4 * met : 5 * 6 * . Redistributions of source code must retain the above copyright 7 * notice, this list of conditions and the following disclaimer. 8 * 9 * . Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * . The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 20 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 24 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 25 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 package cpptools; 30 31 import java.io.BufferedReader; 32 import java.io.File; 33 import java.io.FileReader; 34 import java.io.FilenameFilter; 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.List; 40 import java.util.Locale; 41 import cppast.ParseException; 42 import cppast.Parser; 43 import cppast.ParserVisitor; 44 import cppast.Token; 45 import cpptools.preprocessor.PreProcessor; 46 47 /** 48 * Builds and walks a forrest of abstract syntax trees from a set of given files. 49 * 50 * @author Mathieu Champlon 51 */ 52 public final class Analyzer 53 { 54 private static final String[] DECLARATIONS = 55 { 56 ".h", ".hpp", ".hxx", ".h++", ".inl" 57 }; 58 private static final String[] DEFINITIONS = 59 { 60 ".cpp", ".cxx", ".c++", ".c", ".cc" 61 }; 62 private static final String[] SKIPPED = 63 { 64 ".svn", "CVS", "RCS", "SCCS" 65 }; 66 private static final FilenameFilter FILES_FILTER = new FilenameFilter() 67 { 68 public boolean accept( final File directory, final String name ) 69 { 70 for( String value : SKIPPED ) 71 if( value.equals( name ) ) 72 return false; 73 return true; 74 } 75 }; 76 private final boolean recursive; 77 private final boolean force; 78 private final String prefix; 79 private final List<String> files; 80 private final PreProcessor processor; 81 private final ParserVisitor visitor; 82 private final FileObserver observer; 83 private final EventHandler handler; 84 private final Parser parser; 85 86 /** 87 * Create an analyzer. 88 * 89 * @param options the options 90 * @param visitor the abstract syntax tree visitor 91 * @param observer a file observer 92 * @param handler an event handler 93 */ 94 public Analyzer( final Options options, final ParserVisitor visitor, final FileObserver observer, 95 final EventHandler handler ) 96 { 97 if( observer == null ) 98 throw new IllegalArgumentException( "argument 'observer' is null" ); 99 if( handler == null ) 100 throw new IllegalArgumentException( "argument 'handler' is null" ); 101 if( visitor == null ) 102 throw new IllegalArgumentException( "argument 'visitor' is null" ); 103 this.recursive = options.hasOption( "r" ); 104 this.force = options.hasOption( "k" ); 105 this.prefix = getPrefix( options ); 106 this.processor = createProcessor( options ); 107 this.parser = new Parser( processor ); 108 this.observer = observer; 109 this.handler = handler; 110 this.visitor = visitor; 111 this.files = sort( resolve( options.getArgList() ) ); 112 } 113 114 private String getPrefix( final Options options ) 115 { 116 final List<String> prefixes = options.getOptionPropertyValues( "p" ); 117 if( prefixes.size() > 0 ) 118 return prefixes.get( 0 ); 119 return ""; 120 } 121 122 private PreProcessor createProcessor( final Options options ) 123 { 124 final PreProcessor result = new PreProcessor(); 125 final List<String> defineNames = options.getOptionProperties( "D" ); 126 final List<String> defineValues = options.getOptionPropertyValues( "D" ); 127 for( int i = 0; i < defineNames.size(); ++i ) 128 result.addDefine( defineNames.get( i ), defineValues.get( i ) ); 129 final List<String> macroNames = options.getOptionProperties( "M" ); 130 final List<String> macroValues = options.getOptionPropertyValues( "M" ); 131 for( int i = 0; i < macroNames.size(); ++i ) 132 result.addMacro( macroNames.get( i ), macroValues.get( i ) ); 133 return result; 134 } 135 136 private List<String> resolve( final List<String> inputs ) 137 { 138 final List<String> result = new ArrayList<String>(); 139 for( String input : inputs ) 140 resolve( result, input, true ); 141 return result; 142 } 143 144 private void resolve( final List<String> result, final String string, final boolean processDirectory ) 145 { 146 final File file = new File( string ); 147 if( !file.isDirectory() ) 148 { 149 if( isFrom( string, DECLARATIONS ) || isFrom( string, DEFINITIONS ) ) 150 result.add( string ); 151 } 152 else if( processDirectory ) 153 { 154 final String[] content = file.list( FILES_FILTER ); 155 for( int i = 0; i < content.length; ++i ) 156 { 157 final String filename = string + File.separatorChar + content[i]; 158 resolve( result, filename, recursive ); 159 } 160 } 161 } 162 163 private boolean isFrom( final String string, final String[] strings ) 164 { 165 for( int i = 0; i < strings.length; ++i ) 166 if( string.toLowerCase( Locale.getDefault() ).endsWith( strings[i] ) ) 167 return true; 168 return false; 169 } 170 171 private List<String> sort( final List<String> files ) 172 { 173 Collections.sort( files, new Comparator<String>() 174 { 175 public int compare( final String lhs, final String rhs ) 176 { 177 if( isFrom( lhs, DECLARATIONS ) && isFrom( rhs, DEFINITIONS ) ) 178 return -1; 179 if( isFrom( lhs, DEFINITIONS ) && isFrom( rhs, DECLARATIONS ) ) 180 return 1; 181 return 0; 182 } 183 } ); 184 return files; 185 } 186 187 /** 188 * Run the analyzis. 189 */ 190 public void run() 191 { 192 handler.started(); 193 final int parsed = process( visitor ); 194 handler.finished( parsed, files.size() ); 195 } 196 197 private int process( final ParserVisitor visitor ) 198 { 199 int parsed = 0; 200 for( String filename : files ) 201 { 202 observer.changed( filter( filename ) ); 203 handler.changed( filter( filename ) ); 204 if( process( visitor, filename ) ) 205 ++parsed; 206 else if( !force ) 207 return parsed; 208 } 209 return parsed; 210 } 211 212 private String filter( final String filename ) 213 { 214 if( filename.startsWith( prefix ) ) 215 return filename.substring( prefix.length() ); 216 return filename; 217 } 218 219 private boolean process( final ParserVisitor visitor, final String filename ) 220 { 221 try 222 { 223 parse( visitor, filename ); 224 return true; 225 } 226 catch( ParseException exception ) 227 { 228 final Token token = getToken( exception ); 229 final String message = "Parse error (line " + token.endLine + ", column " + token.endColumn + ")"; 230 handler.error( filename, exception, message ); 231 handler.display( filename, token.beginLine, token.beginColumn ); 232 } 233 catch( Throwable throwable ) 234 { 235 handler.error( filename, throwable, throwable.getMessage() ); 236 } 237 return false; 238 } 239 240 private void parse( final ParserVisitor visitor, final String filename ) throws ParseException, IOException 241 { 242 final BufferedReader reader = new BufferedReader( new FileReader( filename ) ); 243 processor.reset( reader ); 244 parser.ReInit( processor ); 245 parser.translation_unit().jjtAccept( visitor, null ); 246 reader.close(); 247 } 248 249 private Token getToken( final ParseException exception ) 250 { 251 Token token = exception.currentToken.next; 252 while( token.next != null ) 253 token = token.next; 254 return token; 255 } 256 }