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 }