001    /* Copyright (c) 2005-2008, Torbjorn Ekman
002     * All rights reserved.
003     *
004     * Redistribution and use in source and binary forms, with or without
005     * modification, are permitted provided that the following conditions are met:
006     *
007     * 1. Redistributions of source code must retain the above copyright notice,
008     * this list of conditions and the following disclaimer.
009     *
010     * 2. Redistributions in binary form must reproduce the above copyright notice,
011     * this list of conditions and the following disclaimer in the documentation
012     * and/or other materials provided with the distribution.
013     *
014     * 3. Neither the name of the copyright holder nor the names of its
015     * contributors may be used to endorse or promote products derived from this
016     * software without specific prior written permission.
017     *
018     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
022     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028     * POSSIBILITY OF SUCH DAMAGE.
029     */
030    
031    import java.io.File;
032    import java.util.Set;
033    import java.util.HashSet;
034    import java.util.Collections;
035    import java.util.Collection;
036    import java.util.ArrayList;
037    import beaver.*;
038    
039    aspect ClassPath {
040      public interface BytecodeReader {
041        CompilationUnit read(InputStream is, String fullName, Program p)
042            throws FileNotFoundException, IOException;
043      }
044    
045      public interface JavaParser {
046        CompilationUnit parse(InputStream is, String fileName)
047            throws IOException, beaver.Parser.Exception;
048      }
049    
050      protected BytecodeReader Program.bytecodeReader;
051    
052      public void Program.initBytecodeReader(BytecodeReader r) {
053        bytecodeReader = r;
054      }
055    
056      protected JavaParser Program.javaParser;
057    
058      public void Program.initJavaParser(JavaParser p) {
059        javaParser = p;
060      }
061    
062      /**
063       * @return The path to the source file, or the path to the file inside a Jar
064       * file.
065       */
066      syn String CompilationUnit.relativeName() = getClassSource().relativeName();
067    
068      /**
069       * @return The path to the source file, or the enclosing Jar file.
070       */
071      syn String CompilationUnit.pathName() = getClassSource().pathName();
072    
073      /**
074       * @return {@code true} if this compilation unit was parsed from source.
075       */
076      syn boolean CompilationUnit.fromSource() = fromSource;
077    
078      inh CompilationUnit TypeDecl.compilationUnit();
079      eq CompilationUnit.getChild().compilationUnit() = this;
080    
081      /**
082       * Parse the source file and add the compilation unit to the list of
083       * compilation units in the program.
084       *
085       * @param fileName file name of the source file
086       * @return The CompilationUnit representing the source file,
087       * or <code>null</code> if the source file could not be parsed
088       */
089      public CompilationUnit Program.addSourceFile(String fileName) throws IOException {
090        SourceFilePath pathPart = new SourceFilePath(fileName);
091        CompilationUnit cu = pathPart.getCompilationUnit(this, fileName);
092        if (cu != emptyCompilationUnit()) {
093          classPath.addPackage(cu.packageName());
094          addCompilationUnit(cu);
095        }
096        return cu;
097      }
098    
099      /**
100       * Iterate over all source files and on-demand loaded compilation units.
101       */
102      public Iterator<CompilationUnit> Program.compilationUnitIterator() {
103        return new Iterator<CompilationUnit>() {
104          int index = 0;
105          public boolean hasNext() {
106            return index < getNumCompilationUnit();
107          }
108          public CompilationUnit next() {
109            if (getNumCompilationUnit() == index) {
110              throw new java.util.NoSuchElementException();
111            }
112            return getCompilationUnit(index++);
113          }
114          public void remove() {
115            throw new UnsupportedOperationException();
116          }
117        };
118      }
119    
120      /**
121       * Get the input stream for a compilation unit specified using a canonical
122       * name. This is used by the bytecode reader to load nested types.
123       * @param name The canonical name of the compilation unit.
124       */
125      public InputStream Program.getInputStream(String name) {
126        return classPath.getInputStream(name);
127      }
128    
129      private final ClassPath Program.classPath = new ClassPath(this);
130    
131      public class ClassPath {
132    
133        /**
134         * Tracks all currently available packages in the program classpath.
135         */
136        private Set<String> packages = Collections.newSetFromMap(new HashMap<String,Boolean>());
137    
138        private boolean pathsInitialized = false;
139        private ArrayList<PathPart> classPath = new ArrayList<PathPart>();
140        private ArrayList<PathPart> sourcePath = new ArrayList<PathPart>();
141    
142        private final Program program;
143    
144        public ClassPath(Program program) {
145          this.program = program;
146        }
147    
148        /**
149         * Used to make the classpath empty, in case you want more control
150         * over the classpath initialization. Usually you would use
151         * addClassPath to manually setup the classpath after this.
152         */
153        public synchronized void initEmptyPaths() {
154          pathsInitialized = true;
155        }
156    
157        /**
158         * Set up the classpaths (standard + boot classpath).
159         */
160        private synchronized void initPaths() {
161          if (pathsInitialized) {
162            return;
163          }
164          pathsInitialized = true;
165    
166          ArrayList<String> classPaths = new ArrayList<String>();
167          ArrayList<String> sourcePaths = new ArrayList<String>();
168    
169          String[] bootclasspaths;
170          if (program.options().hasValueForOption("-bootclasspath")) {
171            bootclasspaths = program.options().getValueForOption("-bootclasspath").split(File.pathSeparator);
172          } else {
173            bootclasspaths = System.getProperty("sun.boot.class.path").split(File.pathSeparator);
174          }
175          for (int i = 0; i < bootclasspaths.length; i++) {
176            classPaths.add(bootclasspaths[i]);
177          }
178    
179          String[] extdirs;
180          if (program.options().hasValueForOption("-extdirs")) {
181            extdirs = program.options().getValueForOption("-extdirs").split(File.pathSeparator);
182          } else {
183            extdirs = System.getProperty("java.ext.dirs").split(File.pathSeparator);
184          }
185          for (int i = 0; i < extdirs.length; i++) {
186            classPaths.add(extdirs[i]);
187          }
188    
189          String[] userClasses = null;
190          if (program.options().hasValueForOption("-classpath")) {
191            userClasses = program.options().getValueForOption("-classpath").split(File.pathSeparator);
192          } else if (program.options().hasValueForOption("-cp")) {
193            userClasses = program.options().getValueForOption("-cp").split(File.pathSeparator);
194          } else {
195            userClasses = new String[] { "." };
196          }
197          if (!program.options().hasValueForOption("-sourcepath")) {
198            for (int i = 0; i < userClasses.length; i++) {
199              classPaths.add(userClasses[i]);
200              sourcePaths.add(userClasses[i]);
201            }
202          } else {
203            for (int i = 0; i < userClasses.length; i++) {
204              classPaths.add(userClasses[i]);
205            }
206            userClasses = program.options().getValueForOption("-sourcepath").split(File.pathSeparator);
207            for (int i = 0; i < userClasses.length; i++) {
208              sourcePaths.add(userClasses[i]);
209            }
210          }
211    
212          for (String path: classPaths) {
213            PathPart part = PathPart.createClassPath(path);
214            if (part != null) {
215              addClassPath(part);
216            } else if (program.options().verbose()) {
217              System.out.println("Warning: Could not use " + path + " as class path");
218            }
219          }
220          for (String path: sourcePaths) {
221            PathPart part = PathPart.createSourcePath(path);
222            if (part != null) {
223              addSourcePath(part);
224            } else if(program.options().verbose()) {
225              System.out.println("Warning: Could not use " + path + " as source path");
226            }
227          }
228        }
229    
230        /**
231         * Get the input stream for a compilation unit specified using a canonical
232         * name. This is used by the bytecode reader to load nested types.
233         * @param name The canonical name of the compilation unit.
234         */
235        public synchronized InputStream getInputStream(String name) {
236          try {
237            for (Iterator iter = classPath.iterator(); iter.hasNext(); ) {
238              PathPart part = (PathPart) iter.next();
239              ClassSource source = part.findSource(name);
240              if (source != ClassSource.NONE) {
241                return source.openInputStream();
242              }
243            }
244          } catch(IOException e) {
245          }
246          throw new Error("Could not find nested type " + name);
247        }
248    
249        /**
250         * Load a compilation unit from disk based on a classname. A class file is parsed if one exists
251         * matching the classname that is not older than a corresponding source file, otherwise the
252         * source file is selected.
253         * <p>
254         * This method is called by the LibCompilationUnit NTA.  We rely on the result of this method
255         * being cached because it will return a newly parsed compilation unit each time it is called.
256         *
257         * @return the loaded compilation unit, or the provided default compilation unit if no matching
258         * compilation unit was found.
259         */
260        public synchronized CompilationUnit getCompilationUnit(String typeName,
261            CompilationUnit defaultCompilationUnit) {
262          try {
263            initPaths();
264            ClassSource sourcePart = ClassSource.NONE;
265            ClassSource classPart = ClassSource.NONE;
266            for (PathPart part: sourcePath) {
267              sourcePart = part.findSource(typeName);
268              if (sourcePart != ClassSource.NONE) {
269                break;
270              }
271            }
272            for (PathPart part: classPath) {
273              classPart = part.findSource(typeName);
274              if (classPart != ClassSource.NONE) {
275                break;
276              }
277            }
278    
279            if (sourcePart != ClassSource.NONE && (classPart == ClassSource.NONE ||
280                  classPart.lastModified() < sourcePart.lastModified())) {
281              CompilationUnit unit = sourcePart.parseCompilationUnit(program);
282              int index = typeName.lastIndexOf('.');
283              if (index == -1) {
284                return unit;
285              }
286              String pkgName = typeName.substring(0, index);
287              if (pkgName.equals(unit.getPackageDecl())) {
288                return unit;
289              }
290            }
291            if (classPart != ClassSource.NONE) {
292              CompilationUnit unit = classPart.parseCompilationUnit(program);
293              int index = typeName.lastIndexOf('.');
294              if (index == -1) {
295                return unit;
296              }
297              String pkgName = typeName.substring(0, index);
298              if (pkgName.equals(unit.getPackageDecl())) {
299                return unit;
300              }
301            }
302            return defaultCompilationUnit;
303          } catch(IOException e) {
304            // Attributes can't throw checked exceptions, so convert this to an Error.
305            throw new Error(e);
306          }
307        }
308    
309        /**
310         * Add a package name to available package set.
311         */
312        public synchronized void addPackage(String packageName) {
313          int end = packageName.length();
314          while (end > 0 && packages.add(packageName.substring(0, end))) {
315            end = packageName.lastIndexOf('.', end-1);
316          }
317        }
318    
319        /**
320         * Add a path part to the library class path.
321         */
322        public synchronized void addClassPath(PathPart pathPart) {
323          classPath.add(pathPart);
324        }
325    
326        /**
327         * Add a path part to the user class path.
328         */
329        public synchronized void addSourcePath(PathPart pathPart) {
330          sourcePath.add(pathPart);
331        }
332    
333        /**
334         * Quick pass, slow fail. Cache existing package names in a concurrent set.
335         * @return <code>true</code> if there is a package with the given name on
336         * the classpath
337         */
338        public synchronized boolean isPackage(String packageName) {
339          initPaths();
340          if (packages.contains(packageName)) {
341            return true;
342          }
343          for (PathPart part: classPath) {
344            if (part.hasPackage(packageName)) {
345              addPackage(packageName);
346              return true;
347            }
348          }
349          for (PathPart part: sourcePath) {
350            if (part.hasPackage(packageName)) {
351              addPackage(packageName);
352              return true;
353            }
354          }
355          return false;
356        }
357    
358        /**
359         * @return a copy of the source path parts
360         */
361        public synchronized Collection<PathPart> getSourcePath() {
362          return new ArrayList<PathPart>(sourcePath);
363        }
364    
365        /**
366         * @return a copy of the class path parts
367         */
368        public synchronized Collection<PathPart> getClassPath() {
369          return new ArrayList<PathPart>(classPath);
370        }
371      }
372    
373      /**
374       * Load a compilation unit from disk, selecting a class file
375       * if one exists that is not older than a corresponding source
376       * file, otherwise the source file is selected.
377       * <p>
378       * This method is called by the LibCompilationUnit NTA.  We rely on the
379       * result of this method being cached because it will return a newly parsed
380       * compilation unit each time it is called.
381       *
382       * @return the loaded compilation unit, or the empty compilation unit if no compilation unit was
383       * found.
384       */
385      syn lazy CompilationUnit Program.getCompilationUnit(String typeName) =
386          classPath.getCompilationUnit(typeName, emptyCompilationUnit());
387    
388      /**
389       * @return <code>true</code> if there is a package with the given name on
390       * the classpath
391       */
392      public boolean Program.isPackage(String packageName) {
393        return classPath.isPackage(packageName);
394      }
395    
396      private ClassSource CompilationUnit.classSource = ClassSource.NONE;
397      private boolean CompilationUnit.fromSource = false;
398    
399      public void CompilationUnit.setClassSource(ClassSource source) {
400        this.classSource = source;
401      }
402      public ClassSource CompilationUnit.getClassSource() {
403        return classSource;
404      }
405      public void CompilationUnit.setFromSource(boolean value) {
406        this.fromSource = value;
407      }
408    
409      /**
410       * Add a path part to the library class path.
411       */
412      public void Program.addClassPath(PathPart pathPart) {
413        classPath.addClassPath(pathPart);
414      }
415    
416      /**
417       * Add a path part to the user class path.
418       */
419      public void Program.addSourcePath(PathPart pathPart) {
420        classPath.addSourcePath(pathPart);
421      }
422    }