View Javadoc

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 }