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 }