OLD | NEW |
(Empty) | |
| 1 /* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved |
| 2 * |
| 3 * This program is distributed under the terms of |
| 4 * the GNU General Public License Version 2. See the LICENSE file |
| 5 * at the top of the source tree. |
| 6 */ |
| 7 package org.pantsbuild.jmake; |
| 8 |
| 9 import java.io.BufferedReader; |
| 10 import java.io.File; |
| 11 import java.io.FileNotFoundException; |
| 12 import java.io.FileReader; |
| 13 import java.io.IOException; |
| 14 import java.io.InputStream; |
| 15 import java.lang.reflect.InvocationTargetException; |
| 16 import java.lang.reflect.Method; |
| 17 import java.util.ArrayList; |
| 18 import java.util.Collection; |
| 19 import java.util.Collections; |
| 20 import java.util.Enumeration; |
| 21 import java.util.HashMap; |
| 22 import java.util.HashSet; |
| 23 import java.util.LinkedHashMap; |
| 24 import java.util.LinkedHashSet; |
| 25 import java.util.List; |
| 26 import java.util.Map; |
| 27 import java.util.Map.Entry; |
| 28 import java.util.Set; |
| 29 import java.util.StringTokenizer; |
| 30 import java.util.jar.JarEntry; |
| 31 import java.util.jar.JarFile; |
| 32 import java.util.zip.Adler32; |
| 33 |
| 34 /** |
| 35 * This class implements management of the Project Class Directory, automatic tr
acking |
| 36 * of changes and recompilation of .java sources for a project. |
| 37 * |
| 38 * @author Misha Dmitriev |
| 39 * 23 January 2003 |
| 40 */ |
| 41 public class PCDManager { |
| 42 |
| 43 private PCDContainer pcdc; |
| 44 private Map<String,PCDEntry> pcd; // Maps project class names to PCDEntrie
s |
| 45 private String projectJavaAndJarFilesArray[]; |
| 46 private String addedJavaAndJarFilesArray[], removedJavaAndJarFilesArray[],
updatedJavaAndJarFilesArray[]; |
| 47 private List<String> newJavaFiles; |
| 48 private Set<String> updatedJavaFiles; |
| 49 private Set<String> recompiledJavaFiles; |
| 50 private Set<String> updatedClasses; // This set is emptied on every ne
w internal jmake iteration... |
| 51 private Set<String> allUpdatedClasses; // whereas in this one the names o
f all updated classes found during this jmake invocation are stored. |
| 52 private Set<String> updatedAndCheckedClasses; |
| 53 private Set<String> deletedClasses; |
| 54 private Set<String> updatedJarFiles; |
| 55 private Set<String> stableJarFiles; |
| 56 private Set<String> newJarFiles; |
| 57 private Set<String> deletedJarFiles; |
| 58 /* Dependencies from the dependencyFile, if any */ |
| 59 private Map<String, List<String>> extraDependencies; |
| 60 |
| 61 private String destDir; |
| 62 private boolean destDirSpecified; |
| 63 private List<String> javacAddArgs; |
| 64 private Class<?> compilerClass; |
| 65 private Method compileMethod; |
| 66 private String jcExecApp; |
| 67 private Object externalApp; |
| 68 private Method externalCompileSourceFilesMethod; |
| 69 private Adler32 checkSum; |
| 70 private CompatibilityChecker cv; |
| 71 private ClassFileReader cfr; |
| 72 private boolean newProject = false; |
| 73 private String dependencyFile = null; |
| 74 private static boolean backSlashFileSeparator = File.separatorChar != '/'; |
| 75 |
| 76 /**** Interface to the class ****/ |
| 77 /** |
| 78 * Either projectJavaAndJarFilesArray != null and added.. == removed.. == up
datedJavaAndJarFilesArray == null, |
| 79 * or projectJavaAndJarFilesArray == null and one or more of others != null. |
| 80 * When PCDManager is called from Main, this is guaranteed, since separate e
ntrypoint functions initialize |
| 81 * either one or another of the above argument groups, but never both. |
| 82 */ |
| 83 public PCDManager(PCDContainer pcdc, |
| 84 String projectJavaAndJarFilesArray[], |
| 85 String addedJavaAndJarFilesArray[], |
| 86 String removedJavaAndJarFilesArray[], |
| 87 String updatedJavaAndJarFilesArray[], |
| 88 String in_destDir, |
| 89 List<String> javacAddArgs, |
| 90 boolean failOnDependentJar, |
| 91 boolean noWarnOnDependentJar, |
| 92 String dependencyFile) { |
| 93 this.pcdc = pcdc; |
| 94 if (pcdc.pcd == null) { |
| 95 pcd = new LinkedHashMap<String,PCDEntry>(); |
| 96 pcdc.pcd = pcd; |
| 97 newProject = true; |
| 98 } else { |
| 99 pcd = pcdc.pcd; |
| 100 } |
| 101 |
| 102 this.projectJavaAndJarFilesArray = projectJavaAndJarFilesArray; |
| 103 this.addedJavaAndJarFilesArray = addedJavaAndJarFilesArray; |
| 104 this.removedJavaAndJarFilesArray = removedJavaAndJarFilesArray; |
| 105 this.updatedJavaAndJarFilesArray = updatedJavaAndJarFilesArray; |
| 106 this.dependencyFile = dependencyFile; |
| 107 newJavaFiles = new ArrayList<String>(); |
| 108 updatedJavaFiles = new LinkedHashSet<String>(); |
| 109 recompiledJavaFiles = new LinkedHashSet<String>(); |
| 110 updatedAndCheckedClasses = new LinkedHashSet<String>(); |
| 111 deletedClasses = new LinkedHashSet<String>(); |
| 112 allUpdatedClasses = new LinkedHashSet<String>(); |
| 113 |
| 114 updatedJarFiles = new LinkedHashSet<String>(); |
| 115 stableJarFiles = new LinkedHashSet<String>(); |
| 116 newJarFiles = new LinkedHashSet<String>(); |
| 117 deletedJarFiles = new LinkedHashSet<String>(); |
| 118 |
| 119 initializeDestDir(in_destDir); |
| 120 this.javacAddArgs = javacAddArgs; |
| 121 |
| 122 checkSum = new Adler32(); |
| 123 |
| 124 cv = new CompatibilityChecker(this, failOnDependentJar, noWarnOnDependen
tJar); |
| 125 cfr = new ClassFileReader(); |
| 126 } |
| 127 |
| 128 public Collection<PCDEntry> entries() { |
| 129 return pcd.values(); |
| 130 } |
| 131 |
| 132 public ClassFileReader getClassFileReader() { |
| 133 return cfr; |
| 134 } |
| 135 |
| 136 public ClassInfo getClassInfoForName(int verCode, String className) { |
| 137 PCDEntry pcde = pcd.get(className); |
| 138 if (pcde != null) { |
| 139 return getClassInfoForPCDEntry(verCode, pcde); |
| 140 } else { |
| 141 return null; |
| 142 } |
| 143 } |
| 144 |
| 145 public boolean isProjectClass(int verCode, String className) { |
| 146 if (verCode == ClassInfo.VER_OLD) { |
| 147 return pcd.containsKey(className); |
| 148 } else { |
| 149 PCDEntry pcde = pcd.get(className); |
| 150 return (pcde != null && pcde.checkResult != PCDEntry.CV_DELETED); |
| 151 } |
| 152 } |
| 153 |
| 154 /** |
| 155 * Get an instance of ClassInfo (load a class file if necessary) for the giv
en version (old or new) of |
| 156 * the class determined by pcde. For an old class version, always returns a
non-null result; but for a new |
| 157 * version, null is returned if class file is not found. In most of the curr
ent uses of this method null result |
| 158 * is not checked, because it's either called for an old version or it is al
ready known that the .class file |
| 159 * should be present; nevertheless, beware! |
| 160 */ |
| 161 public ClassInfo getClassInfoForPCDEntry(int verCode, PCDEntry pcde) { |
| 162 if (verCode == ClassInfo.VER_OLD) { |
| 163 return pcde.oldClassInfo; |
| 164 } |
| 165 |
| 166 ClassInfo res = pcde.newClassInfo; |
| 167 if (res == null) { |
| 168 byte classFileBytes[]; |
| 169 String classFileFullPath = null; |
| 170 if (pcde.javaFileFullPath.endsWith(".java")) { |
| 171 File classFile = Utils.checkFileForName(pcde.classFileFullPath); |
| 172 if (classFile == null) { |
| 173 return null; // Class file not found. |
| 174 } |
| 175 classFileBytes = Utils.readFileIntoBuffer(classFile); |
| 176 classFileFullPath = pcde.classFileFullPath; |
| 177 } else { |
| 178 try { |
| 179 JarFile jarFile = new JarFile(pcde.javaFileFullPath); |
| 180 JarEntry jarEntry = |
| 181 jarFile.getJarEntry(pcde.className + ".class"); |
| 182 if (jarEntry == null) { |
| 183 return null; |
| 184 } |
| 185 classFileBytes = |
| 186 Utils.readZipEntryIntoBuffer(jarFile, jarEntry); |
| 187 } catch (IOException ex) { |
| 188 throw new PrivateException(ex); |
| 189 } |
| 190 } |
| 191 res = |
| 192 new ClassInfo(classFileBytes, verCode, this, classFileFullPa
th); |
| 193 pcde.newClassInfo = res; |
| 194 } |
| 195 return res; |
| 196 } |
| 197 |
| 198 /** |
| 199 * Returns null if class is compileable (has a .java source) and not recompi
led yet, "" if |
| 200 * class has already been recompiled or has been deleted from project, and t
he class's .jar |
| 201 * name if class comes from a jar, hence is uncompileable. |
| 202 */ |
| 203 public String classAlreadyRecompiledOrUncompileable(String className) { |
| 204 PCDEntry pcde = pcd.get(className); |
| 205 if (pcde == null) { |
| 206 //!!! |
| 207 for (String keyName : pcd.keySet()) { |
| 208 PCDEntry entry = pcd.get(keyName); |
| 209 if (entry.className.equals(className)) { |
| 210 System.out.println("ERROR: inconsistent entry: key = " + |
| 211 keyName + ", name in entry = " + entry.className); |
| 212 } |
| 213 } |
| 214 //!!! |
| 215 throw internalException(className + " not in project when it should
be"); |
| 216 } |
| 217 if (pcde.checkResult == PCDEntry.CV_DELETED) { |
| 218 return ""; |
| 219 } |
| 220 if (pcde.javaFileFullPath.endsWith(".jar")) { |
| 221 return pcde.javaFileFullPath; |
| 222 } else { |
| 223 return (recompiledJavaFiles.contains(pcde.javaFileFullPath) ? "" : n
ull); |
| 224 } |
| 225 } |
| 226 |
| 227 /** |
| 228 * Compiler initialization depends on compiler type specified. |
| 229 * If jcExecApp != null, i.e. an external executable compiler application is
used, and nothing has to be done. |
| 230 * If externalApp != null, that is, jmake is called by an external applicati
on such as Ant, which |
| 231 * manages compilation in its own way, and also nothing has to be done. |
| 232 * Otherwise, load the compiler class and method (either specified through j
cPath, jcMainClass and jcMethod, |
| 233 * or the default one. |
| 234 */ |
| 235 public void initializeCompiler(String jcExecApp, |
| 236 String jcPath, String jcMainClass, String jcMethod, |
| 237 Object externalApp, Method externalCompileSourceFilesMethod) { |
| 238 ClassPath.initializeAllClassPaths(); |
| 239 |
| 240 if (externalApp != null) { |
| 241 this.externalApp = externalApp; |
| 242 this.externalCompileSourceFilesMethod = |
| 243 externalCompileSourceFilesMethod; |
| 244 return; |
| 245 } |
| 246 if (jcExecApp != null) { |
| 247 this.jcExecApp = jcExecApp; |
| 248 return; |
| 249 } |
| 250 |
| 251 if (jcPath == null) { |
| 252 String javaHome = System.getProperty("java.home"); |
| 253 // In my tests it ends with '/jre'. Or it could be ending with '/bin
' as well? Let's assume it can be both and delete |
| 254 // this latter directory. |
| 255 if (javaHome.endsWith(File.separator + "jre") || javaHome.endsWith(F
ile.separator + "bin")) { |
| 256 javaHome = javaHome.substring(0, javaHome.length() - 4); |
| 257 } |
| 258 jcPath = javaHome + "/lib/tools.jar"; |
| 259 } |
| 260 ClassLoader compilerLoader; |
| 261 try { |
| 262 compilerLoader = ClassPath.getClassLoaderForPath(jcPath); |
| 263 } catch (Exception ex) { |
| 264 throw compilerInteractionException("error opening compiler path", ex
, 0); |
| 265 } |
| 266 |
| 267 if (jcMainClass == null) { |
| 268 jcMainClass = "com.sun.tools.javac.Main"; |
| 269 } |
| 270 if (jcMethod == null) { |
| 271 jcMethod = "compile"; |
| 272 } |
| 273 |
| 274 try { |
| 275 compilerClass = compilerLoader.loadClass(jcMainClass); |
| 276 } catch (ClassNotFoundException e) { |
| 277 throw compilerInteractionException("error loading compiler main clas
s " + jcMainClass, e, 0); |
| 278 } |
| 279 |
| 280 Class<?>[] args = new Class<?>[]{String[].class}; |
| 281 try { |
| 282 compileMethod = compilerClass.getMethod(jcMethod, args); |
| 283 } catch (Exception e) { |
| 284 throw compilerInteractionException("error getting method com.sun.too
ls.javac.Main.compile(String args[])", e, 0); |
| 285 } |
| 286 } |
| 287 |
| 288 /** Main entrypoint for this class */ |
| 289 public void run() { |
| 290 Utils.startTiming(Utils.TIMING_SYNCHRO); |
| 291 synchronizeProjectFilesAndPCD(); |
| 292 Utils.stopAndPrintTiming("Synchro", Utils.TIMING_SYNCHRO); |
| 293 Utils.printTiming("of which synchro check file", Utils.TIMING_SYNCHRO_CH
ECK_JAVA_FILES); |
| 294 |
| 295 Utils.startTiming(Utils.TIMING_FIND_UPDATED_JAVA_FILES); |
| 296 findUpdatedJavaAndJarFiles(); |
| 297 Utils.stopAndPrintTiming("findUpdatedJavaAndJarFiles", Utils.TIMING_FIND
_UPDATED_JAVA_FILES); |
| 298 Utils.printTiming("of which classFileObsoleteOrDeleted", Utils.TIMING_CL
ASS_FILE_OBSOLETE_OR_DELETED); |
| 299 |
| 300 // Let's free some memory |
| 301 projectJavaAndJarFilesArray = null; |
| 302 |
| 303 updatedClasses = new LinkedHashSet<String>(); |
| 304 dealWithClassesInUpdatedJarFiles(); |
| 305 |
| 306 int iterNo = 0; |
| 307 int res = 0; |
| 308 while (iterNo == 0 || updatedJavaFiles.size() != 0 || newJavaFiles.size(
) != 0) { |
| 309 // It may happen that we didn't find any updated or new .java files.
However, we still need to enter |
| 310 // this loop because there may be some class files that need compati
bility checking. This can happen |
| 311 // either if somebody had recompiled their sources bypassing jmake,
or if their checking during the |
| 312 // previous invocation of jmake failed, because their dependent code
recompilation failed. |
| 313 if (updatedJavaFiles.size() > 0 || newJavaFiles.size() > 0) { |
| 314 Utils.startTiming(Utils.TIMING_COMPILE); |
| 315 int intermediateRes = recompileUpdatedJavaFiles(); |
| 316 Utils.stopAndPrintTiming("Compile", Utils.TIMING_COMPILE); |
| 317 if (intermediateRes != 0) { |
| 318 res = intermediateRes; |
| 319 } |
| 320 } |
| 321 |
| 322 Utils.startTiming(Utils.TIMING_PDBUPDATE); |
| 323 // New classes can be added to pdb only if compilation was successfu
l, i.e. the new project version is consistent. |
| 324 if (iterNo++ == 0 && res == 0) { |
| 325 findClassFilesForNewJavaAndJarFiles(); |
| 326 findClassFilesForUpdatedJavaFiles(); |
| 327 dealWithNestedClassesForUpdatedJavaFiles(); |
| 328 } |
| 329 Utils.stopAndPrintTiming("Entering new classes in PDB", Utils.TIMING
_PDBUPDATE); |
| 330 |
| 331 updatedJavaFiles.clear(); |
| 332 newJavaFiles.clear(); |
| 333 |
| 334 Utils.startTiming(Utils.TIMING_FIND_UPDATED_CLASSES); |
| 335 findUpdatedClasses(); |
| 336 Utils.stopAndPrintTiming("Find updated classes", Utils.TIMING_FIND_U
PDATED_CLASSES); |
| 337 |
| 338 Utils.startTiming(Utils.TIMING_CHECK_UPDATED_CLASSES); |
| 339 checkDeletedClasses(); |
| 340 checkUpdatedClasses(); |
| 341 Utils.stopAndPrintTiming("Check updated classes", Utils.TIMING_CHECK
_UPDATED_CLASSES); |
| 342 |
| 343 updatedClasses = new LinkedHashSet<String>(); |
| 344 if (ClassPath.getVirtualPath() != null) { |
| 345 if (res != 0) |
| 346 break; |
| 347 } |
| 348 } |
| 349 |
| 350 Utils.startTiming(Utils.TIMING_PDBWRITE); |
| 351 updateClassFilesInfoInPCD(res); |
| 352 pcdc.save(); |
| 353 Utils.stopAndPrintTiming("PDB write", Utils.TIMING_PDBWRITE); |
| 354 |
| 355 if (res != 0) { |
| 356 throw compilerInteractionException("compilation error(s)", null, res
); |
| 357 } |
| 358 } |
| 359 |
| 360 /** |
| 361 * Find the newly-created class files for existing java files. |
| 362 */ |
| 363 private void findClassFilesForUpdatedJavaFiles() { |
| 364 if (dependencyFile == null) |
| 365 return; |
| 366 |
| 367 Set<String> allClasses = new HashSet<String>(); |
| 368 |
| 369 Map<String, List<String>> dependencies = parseDependencyFile(); |
| 370 for (String file : updatedJavaFiles) { |
| 371 List<String> myDeps = dependencies.get(file); |
| 372 if (myDeps != null) { |
| 373 PCDEntry parent = getNamedPCDE(file, dependencies); |
| 374 for (String dependency : myDeps) { |
| 375 allClasses.add(dependency); |
| 376 if (pcd.containsKey(dependency)) |
| 377 continue; |
| 378 findClassFileOnFilesystem(file, parent, dependency, false); |
| 379 } |
| 380 } |
| 381 } |
| 382 for (Map.Entry<String, PCDEntry> entry : pcd.entrySet()) { |
| 383 String cls = entry.getKey(); |
| 384 if (!allClasses.contains(cls)) { |
| 385 PCDEntry pcde = entry.getValue(); |
| 386 if (updatedJavaFiles.contains(pcde.javaFileFullPath)) { |
| 387 deletedClasses.add(cls); |
| 388 } |
| 389 } |
| 390 } |
| 391 } |
| 392 |
| 393 public String[] getAllUpdatedClassesAsStringArray() { |
| 394 String[] res = new String[allUpdatedClasses.size()]; |
| 395 int i = 0; |
| 396 for (String updatedClass : allUpdatedClasses) { |
| 397 res[i++] = updatedClass.replace('/', '.'); |
| 398 } |
| 399 return res; |
| 400 } |
| 401 |
| 402 /** |
| 403 * Synchronize projectJavaAndJarFilesArray and PCD, i.e. leave only those en
tries in the PCD which have their |
| 404 * .java (.jar) files in projectJavaAndJarFilesArray. New .java files in pro
jectJavaAndJarFilesArray (i.e. those |
| 405 * for which there are no entries in the PCD yet) are added to newJavaFiles;
new .jar files are added to newJarFiles. |
| 406 * Alternatively, just use the supplied arrays of added and deleted .java an
d .jar files. |
| 407 * |
| 408 * For entries whose .java files are not in the PCD anymore, try to delete .
class files. We need to do that before |
| 409 * compilation to avoid the situation when a .java file is removed but compi
lation succeeds because the .class file |
| 410 * is still there. |
| 411 * |
| 412 * Unfortunately, we also need to delete all class files for non-nested clas
ses whose names differ from their .java |
| 413 * file name, because we can't tell when they've been removed from their .ja
va files -- but it's only safe to do this |
| 414 * for files that originate from java files that we're compiling this round. |
| 415 * |
| 416 * Upon return from this method, all of the .java and .jar files in the PCD
are known to exist. |
| 417 */ |
| 418 private void synchronizeProjectFilesAndPCD() { |
| 419 if (projectJavaAndJarFilesArray != null) { |
| 420 Set<String> pcdJavaFilesSet = new LinkedHashSet<String>(pcd.size() *
3 / 2); |
| 421 for(PCDEntry entry : entries()) { |
| 422 pcdJavaFilesSet.add(entry.javaFileFullPath); |
| 423 } |
| 424 |
| 425 Set<String> canonicalPJF = |
| 426 new LinkedHashSet<String>(projectJavaAndJarFilesArray.length
* 3 / 2); |
| 427 |
| 428 // Add .java files that are not in PCD to newJavaFiles; add .jar fil
es that are not in PCD to newJarFiles. |
| 429 for (int i = 0; i < projectJavaAndJarFilesArray.length; i++) { |
| 430 String projFileName = projectJavaAndJarFilesArray[i]; |
| 431 Utils.startTiming(Utils.TIMING_SYNCHRO_CHECK_TMP); |
| 432 File projFile = Utils.checkFileForName(projFileName); |
| 433 Utils.stopAndAddTiming(Utils.TIMING_SYNCHRO_CHECK_TMP, Utils.TIM
ING_SYNCHRO_CHECK_JAVA_FILES); |
| 434 if (projFile == null) { |
| 435 throw new PrivateException(new FileNotFoundException("specif
ied source file " + projFileName + " not found.")); |
| 436 } |
| 437 // The main reason for using getAbsolutePath() instead of more r
eliable getCanonicalPath() is the fact that |
| 438 // sometimes users may name the actual files containing Java cod
e in some custom way, and give javac/jmake |
| 439 // symbolic links to these files (that have correct .java names)
instead. getCanonicalPath(), however, returns the |
| 440 // real (i.e. user custom) file name, which will confuse our tes
t below and then javac. |
| 441 String absoluteProjFileName = projFile.getAbsolutePath(); |
| 442 // On Windows, make sure the drive letter is always in lower cas
e |
| 443 if (backSlashFileSeparator) { |
| 444 absoluteProjFileName = |
| 445 Utils.convertDriveLetterToLowerCase(absoluteProjFile
Name); |
| 446 } |
| 447 canonicalPJF.add(absoluteProjFileName); |
| 448 if (!pcdJavaFilesSet.contains(absoluteProjFileName)) { |
| 449 if (absoluteProjFileName.endsWith(".java")) { |
| 450 newJavaFiles.add(absoluteProjFileName); |
| 451 } else if (absoluteProjFileName.endsWith(".jar")) { |
| 452 newJarFiles.add(absoluteProjFileName); |
| 453 } else { |
| 454 throw new PrivateException(new PublicExceptions.InvalidS
ourceFileExtensionException("specified source file " + projFileName + " has an i
nvalid extension (not .java or .jar).")); |
| 455 } |
| 456 } |
| 457 } |
| 458 |
| 459 // Find the entries containing .java or .jar files that are not in p
roject anymore |
| 460 for (Entry<String, PCDEntry> entry : pcd.entrySet()) { |
| 461 String key = entry.getKey(); |
| 462 PCDEntry e = entry.getValue(); |
| 463 e.oldClassInfo.restorePCDM(this); |
| 464 if (canonicalPJF.contains(e.javaFileFullPath)) { |
| 465 if (e.isPackagePrivateClass()) { |
| 466 initializeClassFileFullPath(e); |
| 467 new File(e.classFileFullPath).delete(); |
| 468 } |
| 469 } else { |
| 470 if (ClassPath.getVirtualPath() == null) { |
| 471 deletedClasses.add(key); |
| 472 } else { |
| 473 // Okay, not found locally, but virtual path was defined
, so try it now.... |
| 474 if ( (e.oldClassFileFingerprint == projectJavaAndJarFile
sArray.length && |
| 475 newJavaFiles.size() == 0) || |
| 476 Utils.checkFileForName(e.javaFileFullPath) != nu
ll) |
| 477 { |
| 478 e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER; |
| 479 e.oldClassFileFingerprint = projectJavaAndJarFilesAr
ray.length; |
| 480 } |
| 481 else |
| 482 { |
| 483 String classFound = null; |
| 484 String sourceFound = null; |
| 485 // Find source and class file via virtual path |
| 486 String path = ClassPath.getVirtualPath(); |
| 487 // TODO(Eric Ayers): IntelliJ static analysis shows
several useless |
| 488 // expressions that make this loop a no-op. |
| 489 for (StringTokenizer st = new StringTokenizer(path,
File.pathSeparator); |
| 490 !(classFound != null && sourceFound != null) &&
st.hasMoreTokens();) |
| 491 { |
| 492 String fullPath = st.nextToken()+File.separator+
e.className; |
| 493 if (sourceFound != null && new File(fullPath+".j
ava").exists()) |
| 494 { |
| 495 sourceFound = fullPath + ".java"; |
| 496 } |
| 497 if (classFound != null && new File(fullPath+".cl
ass").exists()) |
| 498 { |
| 499 classFound = fullPath + ".class"; |
| 500 } |
| 501 } |
| 502 // TODO(Eric Ayers): IntelliJ static analysis shows
that this expression |
| 503 // is always true. |
| 504 if (classFound == null) |
| 505 { |
| 506 deletedClasses.add(key); |
| 507 if (e.javaFileFullPath.endsWith(".jar")) |
| 508 { |
| 509 deletedJarFiles.add(e.javaFileFullPath); |
| 510 } |
| 511 else |
| 512 { |
| 513 initializeClassFileFullPath(e); |
| 514 (new File(e.classFileFullPath)).delete(); |
| 515 } |
| 516 } |
| 517 else if (sourceFound != null) |
| 518 { |
| 519 newJavaFiles.add(sourceFound); |
| 520 e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER; |
| 521 e.oldClassFileFingerprint = projectJavaAndJarFil
esArray.length; |
| 522 } |
| 523 else |
| 524 { |
| 525 classFound = classFound.replace('/', File.separa
torChar); |
| 526 throw new PrivateException(new FileNotFoundExcep
tion("deleted class " + classFound + " still exists.")); |
| 527 } |
| 528 } |
| 529 } |
| 530 if (e.javaFileFullPath.endsWith(".jar")) { |
| 531 deletedJarFiles.add(e.javaFileFullPath); |
| 532 } else { // Try to delete a class file for the removed proj
ect class. |
| 533 initializeClassFileFullPath(e); |
| 534 (new File(e.classFileFullPath)).delete(); |
| 535 } |
| 536 } |
| 537 } |
| 538 } else { // projectJavaAndJarFilesArray == null - use supplied arrays of
added and removed .java and .jar files |
| 539 if (addedJavaAndJarFilesArray != null) { |
| 540 for (String fileName : addedJavaAndJarFilesArray) { |
| 541 fileName = fileName.intern(); |
| 542 if (fileName.endsWith(".java")) { |
| 543 newJavaFiles.add(fileName); |
| 544 } else if (fileName.endsWith(".jar")) { |
| 545 newJarFiles.add(fileName); |
| 546 } else { |
| 547 throw new PrivateException(new PublicExceptions.InvalidS
ourceFileExtensionException( |
| 548 "specified source file " + fileName + " has an inval
id extension (not .java or .jar).")); |
| 549 } |
| 550 } |
| 551 } |
| 552 |
| 553 Set<String> removedJavaAndJarFilesSet = null; |
| 554 if (removedJavaAndJarFilesArray != null) { |
| 555 removedJavaAndJarFilesSet = new LinkedHashSet<String>(); |
| 556 for (String fileName : removedJavaAndJarFilesArray) { |
| 557 fileName = fileName.intern(); |
| 558 removedJavaAndJarFilesSet.add(fileName); |
| 559 if (fileName.endsWith(".jar")) { |
| 560 deletedJarFiles.add(fileName); |
| 561 } |
| 562 } |
| 563 } |
| 564 |
| 565 for (Entry<String, PCDEntry> entry : pcd.entrySet()) { |
| 566 String key = entry.getKey(); |
| 567 PCDEntry e = entry.getValue(); |
| 568 e.oldClassInfo.restorePCDM(this); |
| 569 if (removedJavaAndJarFilesSet != null && |
| 570 removedJavaAndJarFilesSet.contains(e.javaFileFullPath))
{ |
| 571 deletedClasses.add(key); |
| 572 if (!e.javaFileFullPath.endsWith(".jar")) { // Try to delet
e a class file for the removed project class. |
| 573 initializeClassFileFullPath(e); |
| 574 (new File(e.classFileFullPath)).delete(); |
| 575 } |
| 576 } |
| 577 } |
| 578 } |
| 579 } |
| 580 |
| 581 /** |
| 582 * In the end of run, update the information in the project database for the
class files which have |
| 583 * been updated and checked, or deleted. If compilationResult == 0, i.e. all
recompilations were |
| 584 * successful, information for new versions of all of the classes is made pe
rmanent, and entries |
| 585 * for deleted classes are removed permanently. Otherwise, information is up
dated only for those |
| 586 * classes whose old and new versions were found source compatible. |
| 587 */ |
| 588 private void updateClassFilesInfoInPCD(int compilationResult) { |
| 589 // If the project appears to be inconsistent after changes, make a preli
minary pass that will deal with enclosing |
| 590 // classes for deleted nested classes. The problem with them can be as f
ollows: we delete a nested class C$X, |
| 591 // which is still referenced from somewhere. However, C has not changed
at all or at least incompatibly, and |
| 592 // thus we update its PCDEntry, which now does not reference C$X. Other
parts of jmake require that a nested |
| 593 // class is always referenced from its directly enclusing class, thus to
keep the PCD consistent we have to remove |
| 594 // C$X from the PCD. On the next invocation of jmake, C$X is not in the
PDB at all, and thus any classes that |
| 595 // may still reference it and have not been updated are not checked => p
roject becomes inconsistent. We could do |
| 596 // better by immediately marking enclosing classes incompatible once we
detect that a deleted nested class is |
| 597 // really referenced from somewhere, but the solution below seems to be
more robust. |
| 598 if (compilationResult != 0) { |
| 599 for (String className : updatedAndCheckedClasses) { |
| 600 PCDEntry entry = pcd.get(className); |
| 601 if (entry.checkResult == PCDEntry.CV_DELETED && |
| 602 !"".equals(entry.oldClassInfo.directlyEnclosingClass)) { |
| 603 PCDEntry enclEntry = |
| 604 pcd.get(entry.oldClassInfo.directlyEnclosingClass); |
| 605 enclEntry.checkResult = PCDEntry.CV_INCOMPATIBLE; |
| 606 } |
| 607 } |
| 608 } |
| 609 |
| 610 for (String className : updatedAndCheckedClasses) { |
| 611 PCDEntry entry = pcd.get(className); |
| 612 if (entry.checkResult == PCDEntry.CV_UNCHECKED) { |
| 613 continue; |
| 614 } |
| 615 if (ClassPath.getVirtualPath() != null) { |
| 616 if (entry.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) { |
| 617 continue; |
| 618 } |
| 619 } |
| 620 if (entry.checkResult == PCDEntry.CV_DELETED) { |
| 621 if (compilationResult == 0) { |
| 622 pcd.remove(className); // Only if consistency checking is o
k, a deleted class can be safely removed from the PCD |
| 623 } |
| 624 } else if (entry.checkResult == PCDEntry.CV_COMPATIBLE || |
| 625 entry.checkResult == PCDEntry.CV_NEW || |
| 626 (entry.checkResult == PCDEntry.CV_INCOMPATIBLE && compilatio
nResult == 0)) { |
| 627 if (entry.newClassInfo == null) { // "Safety net" for the (hope
fully unlikely) case we overlooked something before... |
| 628 Utils.printWarningMessage("Warning: internal information inc
onsistency detected during pdb updating"); |
| 629 Utils.printWarningMessage(Utils.REPORT_PROBLEM); |
| 630 Utils.printWarningMessage("Class name: " + className); |
| 631 if (entry.checkResult == PCDEntry.CV_NEW) { |
| 632 pcd.remove(className); |
| 633 } else { |
| 634 continue; |
| 635 } |
| 636 } |
| 637 entry.oldClassFileLastModified = entry.newClassFileLastModified; |
| 638 entry.oldClassFileFingerprint = entry.newClassFileFingerprint; |
| 639 entry.oldClassInfo = entry.newClassInfo; |
| 640 } |
| 641 } |
| 642 } |
| 643 |
| 644 /** |
| 645 * Find all .java files on the filesystem, for which the .class file does n
ot exist |
| 646 * or is newer than the .java file. Also find all .jar files for which the t
imestamp |
| 647 * has changed. Alternatively, just use the supplied array of updated .java/
.jar files. |
| 648 */ |
| 649 private void findUpdatedJavaAndJarFiles() { |
| 650 boolean projectSpecifiedAsAllSources = |
| 651 projectJavaAndJarFilesArray != null; |
| 652 for (PCDEntry entry : entries()) { |
| 653 if (deletedClasses.contains(entry.className)) { |
| 654 continue; |
| 655 } |
| 656 if (entry.javaFileFullPath.endsWith(".java")) { |
| 657 initializeClassFileFullPath(entry); |
| 658 if (projectSpecifiedAsAllSources) { |
| 659 if (ClassPath.getVirtualPath() != null) { |
| 660 String paths[] = ClassPath.getVirtualPath().split(File.p
athSeparator); |
| 661 String tmpClassName = entry.className; |
| 662 tmpClassName = tmpClassName.replaceAll("\\Q$\\E.*$", "")
; |
| 663 for (int i=0; i<paths.length; i++) { |
| 664 String tmpFilename = paths[i] + File.separator + tmp
ClassName + ".java"; |
| 665 File tmpFile = new File(tmpFilename); |
| 666 if (tmpFile.exists()) { |
| 667 entry.javaFileFullPath = tmpFile.getAbsolutePath
(); |
| 668 break; |
| 669 } |
| 670 } |
| 671 } |
| 672 Utils.startTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP); |
| 673 if (classFileObsoleteOrDeleted(entry)) { |
| 674 updatedJavaFiles.add(entry.javaFileFullPath); |
| 675 } |
| 676 Utils.stopAndAddTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP,
Utils.TIMING_CLASS_FILE_OBSOLETE_OR_DELETED); |
| 677 } |
| 678 entry.checked = true; |
| 679 } else { // Class coming from a .jar file. Mark this entry as check
ed only if its JAR hasn't changed |
| 680 if (projectJavaAndJarFilesArray != null) { |
| 681 entry.checked = !checkJarFileForUpdate(entry); |
| 682 } |
| 683 } |
| 684 } |
| 685 |
| 686 // Lists of updated/added/deleted source files specified instead of a fu
ll list of project sources |
| 687 if (!projectSpecifiedAsAllSources && updatedJavaAndJarFilesArray != null
) { |
| 688 for (int i = 0; i < updatedJavaAndJarFilesArray.length; i++) { |
| 689 if (updatedJavaAndJarFilesArray[i].endsWith(".java")) { |
| 690 updatedJavaFiles.add(updatedJavaAndJarFilesArray[i]); |
| 691 } else { |
| 692 updatedJarFiles.add(updatedJavaAndJarFilesArray[i]); |
| 693 } |
| 694 } |
| 695 } |
| 696 } |
| 697 |
| 698 private boolean classFileObsoleteOrDeleted(PCDEntry entry) { |
| 699 if (ClassPath.getVirtualPath() != null) { |
| 700 File file1 = new File(entry.javaFileFullPath); |
| 701 if (!file1.exists()) |
| 702 throw new PrivateException(new FileNotFoundException("specified
source file " + |
| 703 entry.javaFileFullPath + " not found.")); |
| 704 if (file1.lastModified() < entry.oldClassFileLastModified) |
| 705 { |
| 706 return false; |
| 707 } |
| 708 } |
| 709 File classFile = Utils.checkFileForName(entry.classFileFullPath); |
| 710 if (classFile == null || !classFile.exists()) { |
| 711 return true; // Class file has been deleted |
| 712 } |
| 713 File javaFile = new File(entry.javaFileFullPath); // Guaranteed to exist
at this point |
| 714 if (classFile.lastModified() <= javaFile.lastModified()) { |
| 715 return true; |
| 716 } |
| 717 return false; |
| 718 } |
| 719 |
| 720 private boolean checkJarFileForUpdate(PCDEntry entry) { |
| 721 String jarFileName = entry.javaFileFullPath; |
| 722 if (stableJarFiles.contains(jarFileName)) { |
| 723 return false; |
| 724 } else if (updatedJarFiles.contains(jarFileName) || |
| 725 newJarFiles.contains(jarFileName) || |
| 726 deletedJarFiles.contains(jarFileName)) { |
| 727 return true; |
| 728 } else { |
| 729 File jarFile = new File(jarFileName); // Guaranteed to exist at this
point. |
| 730 if (entry.oldClassFileLastModified != jarFile.lastModified()) { |
| 731 updatedJarFiles.add(jarFileName); |
| 732 return true; |
| 733 } else { |
| 734 stableJarFiles.add(jarFileName); |
| 735 return false; |
| 736 } |
| 737 } |
| 738 } |
| 739 |
| 740 public int recompileUpdatedJavaFiles() { |
| 741 if (externalApp != null) { |
| 742 return recompileUpdatedJavaFilesUsingExternalMethod(); |
| 743 } else { |
| 744 return recompileUpdatedJavaFilesOurselves(); |
| 745 } |
| 746 } |
| 747 |
| 748 private int recompileUpdatedJavaFilesOurselves() { |
| 749 int filesNo = updatedJavaFiles.size() + newJavaFiles.size(); |
| 750 int addArgsNo = javacAddArgs.size(); |
| 751 int argsNo = addArgsNo + filesNo + 2; |
| 752 String compilerBootClassPath, compilerExtDirs; |
| 753 if ((compilerBootClassPath = ClassPath.getCompilerBootClassPath()) != nu
ll) { |
| 754 argsNo += 2; |
| 755 } |
| 756 if ((compilerExtDirs = ClassPath.getCompilerExtDirs()) != null) { |
| 757 argsNo += 2; |
| 758 } |
| 759 if (jcExecApp != null) { |
| 760 argsNo++; |
| 761 } |
| 762 String args[] = new String[argsNo]; |
| 763 int pos = 0; |
| 764 if (jcExecApp != null) { |
| 765 args[pos++] = jcExecApp; |
| 766 } |
| 767 for (int i = 0; i < addArgsNo; i++) { |
| 768 args[pos++] = javacAddArgs.get(i); |
| 769 } |
| 770 args[pos++] = "-classpath"; |
| 771 args[pos++] = ClassPath.getCompilerUserClassPath(); |
| 772 if (compilerBootClassPath != null) { |
| 773 args[pos++] = "-bootclasspath"; |
| 774 args[pos++] = compilerBootClassPath; |
| 775 } |
| 776 if (compilerExtDirs != null) { |
| 777 args[pos++] = "-extdirs"; |
| 778 args[pos++] = compilerExtDirs; |
| 779 } |
| 780 if (!newProject) { |
| 781 Utils.printInfoMessage("Recompiling source files:"); |
| 782 } |
| 783 for (String javaFileFullPath : updatedJavaFiles) { |
| 784 if (!newProject) { |
| 785 Utils.printInfoMessage(javaFileFullPath); |
| 786 } |
| 787 recompiledJavaFiles.add(args[pos++] = javaFileFullPath); |
| 788 } |
| 789 for (int j = 0; j < newJavaFiles.size(); j++) { |
| 790 String javaFileFullPath = newJavaFiles.get(j); |
| 791 if (!newProject) { |
| 792 Utils.printInfoMessage(javaFileFullPath); |
| 793 } |
| 794 recompiledJavaFiles.add(args[pos++] = javaFileFullPath); |
| 795 } |
| 796 |
| 797 if (jcExecApp == null) { // Executing javac or some other compiler with
in the same JVM |
| 798 Object reflectArgs[] = new Object[1]; |
| 799 reflectArgs[0] = args; |
| 800 try { |
| 801 Object dummy = compilerClass.newInstance(); |
| 802 Integer res = (Integer) compileMethod.invoke(dummy, reflectArgs)
; |
| 803 return res.intValue(); |
| 804 } catch (Exception e) { |
| 805 throw compilerInteractionException("exception thrown when trying
to invoke the compiler method", e, 0); |
| 806 } |
| 807 } else { // Executing an external Java compiler, such as jikes |
| 808 int exitCode = 0; |
| 809 try { |
| 810 Process p = Runtime.getRuntime().exec(args); |
| 811 InputStream pErr = p.getErrorStream(); |
| 812 InputStream pOut = p.getInputStream(); |
| 813 boolean terminated = false; |
| 814 |
| 815 while (!terminated) { |
| 816 try { |
| 817 exitCode = p.exitValue(); |
| 818 terminated = true; |
| 819 } catch (IllegalThreadStateException itse) { // Process not
yet terminated, wait for some time |
| 820 Utils.ignore(itse); |
| 821 Utils.delay(100); |
| 822 } |
| 823 try { |
| 824 Utils.readAndPrintBytesFromStream(pErr, System.err); |
| 825 Utils.readAndPrintBytesFromStream(pOut, System.out); |
| 826 } catch (IOException ioe1) { |
| 827 throw compilerInteractionException("I/O error when readi
ng the compiler application output", ioe1, exitCode); |
| 828 } |
| 829 } |
| 830 return exitCode; |
| 831 } catch (IOException ioe2) { |
| 832 throw compilerInteractionException("I/O error when trying to inv
oke the compiler application", ioe2, exitCode); |
| 833 } |
| 834 } |
| 835 } |
| 836 |
| 837 /** Execution under complete control of external app - use externally suppli
ed method to recompile classes */ |
| 838 private int recompileUpdatedJavaFilesUsingExternalMethod() { |
| 839 int filesNo = updatedJavaFiles.size() + newJavaFiles.size(); |
| 840 String[] fileNames = new String[filesNo]; |
| 841 int i = 0; |
| 842 for (String updatedFile : updatedJavaFiles) { |
| 843 recompiledJavaFiles.add(fileNames[i] = updatedFile); |
| 844 } |
| 845 for (int j = 0; j < newJavaFiles.size(); j++) { |
| 846 recompiledJavaFiles.add(fileNames[i++] = newJavaFiles.get(j)); |
| 847 } |
| 848 |
| 849 try { |
| 850 Integer res = |
| 851 (Integer) externalCompileSourceFilesMethod.invoke(externalAp
p, new Object[]{fileNames}); |
| 852 return res.intValue(); |
| 853 } catch (IllegalAccessException e1) { |
| 854 throw compilerInteractionException("compiler method is not accessibl
e", e1, 0); |
| 855 } catch (IllegalArgumentException e2) { |
| 856 throw compilerInteractionException("illegal arguments passed to comp
iler method", e2, 0); |
| 857 } catch (InvocationTargetException e3) { |
| 858 throw compilerInteractionException("exception when executing the com
piler method", e3, 0); |
| 859 } |
| 860 } |
| 861 |
| 862 /** |
| 863 * For each .java file from newJavaFiles, find all of the .class files, the
names of which we can |
| 864 * logically deduce (a top-level class with the same name, and all of the ne
sted classes), |
| 865 * and put the info on them into the PCD. Also include any class files from
the dependencyFile, |
| 866 * if any. For each .jar file from newJarFiles, find all of the .class files
in that archive and |
| 867 * put info on them into the PCD. |
| 868 */ |
| 869 private void findClassFilesForNewJavaAndJarFiles() { |
| 870 for (String javaFileFullPath : newJavaFiles) { |
| 871 PCDEntry pcde = |
| 872 findClassFileOnFilesystem(javaFileFullPath, null, null, fals
e); |
| 873 |
| 874 if (pcde == null) { |
| 875 // .class file not found - possible compilation error |
| 876 if (missingClassIsOk(javaFileFullPath)) { |
| 877 continue; |
| 878 } else { |
| 879 throw new PrivateException(new PublicExceptions.ClassNameMis
matchException( |
| 880 "Could not find class file for " + javaFileFullPath)
); |
| 881 } |
| 882 } |
| 883 Set<String> entries = new HashSet<String>(); |
| 884 if (pcde.checkResult == PCDEntry.CV_NEW) { // It's really a new .ja
va file, not a moved one |
| 885 entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, false
)); |
| 886 } else { |
| 887 entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, true)
); |
| 888 } |
| 889 entries.add(pcde.className); |
| 890 if (dependencyFile != null) { |
| 891 Map<String, List<String>> dependencies = parseDependencyFile(); |
| 892 List<String> myDeps = dependencies.get(javaFileFullPath); |
| 893 if (myDeps != null) { |
| 894 for (String dependency : myDeps) { |
| 895 if (entries.contains(dependency)) |
| 896 continue; |
| 897 findClassFileOnFilesystem(javaFileFullPath, pcde, |
| 898 dependency, false); |
| 899 } |
| 900 } |
| 901 } |
| 902 } |
| 903 |
| 904 for (String newJarFile : newJarFiles) { |
| 905 processAllClassesFromJarFile(newJarFile); |
| 906 } |
| 907 } |
| 908 |
| 909 /** |
| 910 * Parse an extra dependency file. The format of the file is a series of li
nes, |
| 911 * each consisting of: |
| 912 * SourceFileName.java -> ClassName |
| 913 * (these file names are relative to destDir) |
| 914 */ |
| 915 private Map<String, List<String>> parseDependencyFile() { |
| 916 if (!destDirSpecified) |
| 917 throw new RuntimeException("Dependency files require destDir"); |
| 918 if (extraDependencies != null) |
| 919 return extraDependencies; |
| 920 BufferedReader in = null; |
| 921 try { |
| 922 extraDependencies = new HashMap<String, List<String>>(); |
| 923 in = new BufferedReader(new FileReader(dependencyFile)); |
| 924 int lineNumber = 0; |
| 925 while (true) { |
| 926 lineNumber ++; |
| 927 String line = in.readLine(); |
| 928 if (line == null) |
| 929 break; |
| 930 String[] parts = line.split("->"); |
| 931 if (parts.length != 2) { |
| 932 throw new RuntimeException("Failed to parse line " + lineNum
ber + " of " + dependencyFile |
| 933 + ". Expected {foo.java} -> {clas
sname}."); |
| 934 } |
| 935 String src = parts[0].trim(); |
| 936 src = new File(destDir, src).getCanonicalPath(); |
| 937 String cls = parts[1].trim(); |
| 938 List<String> classes = extraDependencies.get(src); |
| 939 if (classes == null) { |
| 940 classes = new ArrayList<String>(); |
| 941 extraDependencies.put(src, classes); |
| 942 } |
| 943 cls = cls.substring(0, cls.length() - 6); // strip trailing ".cl
ass" |
| 944 classes.add(cls); |
| 945 } |
| 946 } catch (IOException e) { |
| 947 throw new PrivateException(e); |
| 948 } finally { |
| 949 if (in != null) |
| 950 try { |
| 951 in.close(); |
| 952 } catch (IOException e) { |
| 953 throw new RuntimeException(e); |
| 954 } |
| 955 } |
| 956 return extraDependencies; |
| 957 } |
| 958 |
| 959 /** |
| 960 * In most cases we want to fail the build if a class cannot be found. |
| 961 * |
| 962 * However there is one common valid case where a .java file might not conta
in |
| 963 * a class: package-info.java files. |
| 964 * |
| 965 * See this doc for more info: http://docs.oracle.com/javase/specs/jls/se7/h
tml/jls-7.html |
| 966 */ |
| 967 private boolean missingClassIsOk(String javaFileFullPath) { |
| 968 return javaFileFullPath != null && "package-info.java".equals(new File(j
avaFileFullPath).getName()); |
| 969 } |
| 970 |
| 971 /** |
| 972 * Find the .class file for the given javaFileFullPath and create a new PCDE
ntry for it. |
| 973 * If enclosingClassPCDE is null, the named top-level class for the given .j
ava file is looked up. |
| 974 * Otherwise, the specified class specified by nestedClassFullName is looked
up. |
| 975 */ |
| 976 private PCDEntry findClassFileOnFilesystem(String javaFileFullPath, PCDEntry
enclosingClassPCDE, String nestedClassFullName, boolean isNested) { |
| 977 String classFileFullPath = null; |
| 978 String fullClassName; |
| 979 File classFile = null; |
| 980 |
| 981 if (enclosingClassPCDE == null) { // Looking for a top-level class. May
need to locate an appropriate directory. |
| 982 // Remove the ".java" suffix. A Windows disk-name prefix, such as 'c
:', will be cut off later automatically |
| 983 fullClassName = |
| 984 javaFileFullPath.substring(0, javaFileFullPath.length() - 5)
; |
| 985 if (destDirSpecified) { |
| 986 // Search for the .class file. We first assume the longest possi
ble name. In case of failure, |
| 987 // we cut the assumed top-most package from it and repeat the se
arch. |
| 988 while (classFile == null) { |
| 989 classFileFullPath = destDir + fullClassName + ".class"; |
| 990 classFile = Utils.checkFileForName(classFileFullPath); |
| 991 if (classFile == null) { |
| 992 int cutIndex = fullClassName.indexOf(File.separatorChar)
; |
| 993 if (cutIndex == -1) { |
| 994 // Most probably, there was an error during compilat
ion of this file. |
| 995 // This does not prevent us from continuing. |
| 996 Utils.printWarningMessage("Warning: unable to find .
class file corresponding to source " + javaFileFullPath + ": expected " + classF
ileFullPath); |
| 997 |
| 998 return null; |
| 999 } |
| 1000 fullClassName = fullClassName.substring(cutIndex + 1); |
| 1001 } |
| 1002 } |
| 1003 } else { |
| 1004 classFileFullPath = fullClassName + ".class"; |
| 1005 classFile = Utils.checkFileForName(classFileFullPath); |
| 1006 if (classFile == null) { |
| 1007 Utils.printWarningMessage("Warning: unable to find .class fi
le corresponding to source " + javaFileFullPath); |
| 1008 return null; |
| 1009 } |
| 1010 } |
| 1011 } else { // Looking for a nested class, which always sits in the same d
irectory as its enclosing class |
| 1012 classFileFullPath = |
| 1013 Utils.getClassFileFullPathForNestedClass(enclosingClassPCDE.
classFileFullPath, nestedClassFullName); |
| 1014 classFile = Utils.checkFileForName(classFileFullPath); |
| 1015 if (classFile == null) { |
| 1016 Utils.printWarningMessage("Warning: unable to find .class file c
orresponding to nested class " + nestedClassFullName); |
| 1017 return null; |
| 1018 } |
| 1019 fullClassName = nestedClassFullName; |
| 1020 } |
| 1021 |
| 1022 if (backSlashFileSeparator) { |
| 1023 fullClassName = fullClassName.replace(File.separatorChar, '/'); |
| 1024 } |
| 1025 |
| 1026 byte classFileBytes[] = Utils.readFileIntoBuffer(classFile); |
| 1027 ClassInfo classInfo = |
| 1028 new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, classFile
FullPath); |
| 1029 if (isNested) { |
| 1030 if (!classInfo.directlyEnclosingClass.equals(enclosingClassPCDE.newC
lassInfo.name)) { |
| 1031 // Check if the above strings are like A and A$1. If so, there i
s actually no problem - the correct |
| 1032 // answer is A$1. The reason why just A was determined as a dire
ctly enclosing class when parsing |
| 1033 // class classInfo is due to the ambiguous interpretation of nam
es like A$1$B. Such a name may mean |
| 1034 // (1) a non-member local nested class B of A, or (2) a member c
lass B of an anonymous nested class A$1. |
| 1035 // When parsing any non-toplevel class, the first interpretation
is always used. |
| 1036 // NOTE FOR JDK 1.5 - starting from this version, there is no am
biguity anymore. |
| 1037 // (1) will be called A$1B, and (2) will still be A$1$B |
| 1038 String a = classInfo.directlyEnclosingClass; |
| 1039 String ad1 = enclosingClassPCDE.newClassInfo.name; |
| 1040 if (!((classInfo.javacTargetRelease == Utils.JAVAC_TARGET_RELEAS
E_OLDEST) && |
| 1041 (ad1.startsWith(a + "$") && Character.isDigit(ad1.charAt
(a.length() + 1))))) { |
| 1042 throw new PrivateException(new PublicExceptions.ClassFilePar
seException( |
| 1043 "Enclosing class names for class " + classInfo.name
+ " don't match:\n" + |
| 1044 classInfo.directlyEnclosingClass + " and " + enclosi
ngClassPCDE.newClassInfo.name)); |
| 1045 } |
| 1046 } |
| 1047 } |
| 1048 |
| 1049 // If dest dir was specified, check if the deduced name is equal to the
one in this class (in this case |
| 1050 // they should necessarily match). Otherwise, without parsing the .java
file, we can't reliably say what the |
| 1051 // full class name (actually, its package part) should be - so we just n
ote the name. |
| 1052 if (destDirSpecified) { |
| 1053 if (!fullClassName.equals(classInfo.name)) { |
| 1054 throw new PrivateException(new PublicExceptions.ClassNameMismatc
hException( |
| 1055 "Error: deduced class name is different from the real on
e for source " + |
| 1056 javaFileFullPath + "\n" + fullClassName + " and " + clas
sInfo.name)); |
| 1057 } |
| 1058 } else { |
| 1059 fullClassName = classInfo.name; |
| 1060 } |
| 1061 |
| 1062 if (enclosingClassPCDE != null) { |
| 1063 javaFileFullPath = enclosingClassPCDE.javaFileFullPath; |
| 1064 } |
| 1065 long classFileLastMod = classFile.lastModified(); |
| 1066 long classFileFP = computeFP(classFileBytes); |
| 1067 |
| 1068 if (pcd.containsKey(fullClassName)) { |
| 1069 PCDEntry pcde = pcd.get(fullClassName); |
| 1070 // If this entry has already been checked, it's a second entry for t
he same class, which is illegal. |
| 1071 if (pcde.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) { |
| 1072 // Newer copy of same file found in closer layer |
| 1073 // Reset to CV_UNCHECKED and skip redundnacy check |
| 1074 // as we know this would be redundant |
| 1075 pcde.checkResult = PCDEntry.CV_UNCHECKED; |
| 1076 } else { |
| 1077 if (pcde.checked) { |
| 1078 throw new PrivateException(new PublicExceptions.DoubleEntryE
xception( |
| 1079 "Two entries for class " + classInfo.name + " detect
ed: " + pcde.javaFileFullPath + " and " + javaFileFullPath)); |
| 1080 } |
| 1081 } |
| 1082 // Otherwise, it means that the .java file for this class has been m
oved. jmake initially interprets |
| 1083 // a new source file name as a new class, and it's only at this poin
t that we can actually see that it was |
| 1084 // only a move. We update javaFileFullPath for nested classes after
we return from here. |
| 1085 pcde.javaFileFullPath = javaFileFullPath; |
| 1086 pcde.classFileFullPath = classFileFullPath; |
| 1087 pcde.newClassInfo = classInfo; |
| 1088 if (deletedClasses.contains(fullClassName)) { |
| 1089 deletedClasses.remove(fullClassName); |
| 1090 } |
| 1091 return pcde; |
| 1092 } |
| 1093 |
| 1094 PCDEntry pcde = new PCDEntry(fullClassName, |
| 1095 javaFileFullPath, |
| 1096 classFileFullPath, classFileLastMod, classFileFP, |
| 1097 classInfo); |
| 1098 pcde.checkResult = PCDEntry.CV_NEW; // So that later it's promo
ted into oldClassInfo correctly |
| 1099 updatedAndCheckedClasses.add(fullClassName); // So that the above happen
s |
| 1100 pcd.put(fullClassName, pcde); |
| 1101 return pcde; |
| 1102 } |
| 1103 |
| 1104 /** |
| 1105 * For the given class, find all direct nested classes (which may include re
ading their .class files from the |
| 1106 * class path) and set their access flags (contained in this, enclosing clas
s, object) appropriately. If |
| 1107 * this class is a one coming from a .java source, repeat the procedure for
each nested class in turn. |
| 1108 * Otherwise, i.e. if a class comes from a .jar, don't bother, since we will
come across each of these |
| 1109 * classes anyway - when scanning their .jar. If 'move' parameter is true, i
t means that this method is called for |
| 1110 * a class that is not new, but has been moved (and possibly updated). |
| 1111 */ |
| 1112 private Set<String> findAndUpdateAllNestedClassesForClass(PCDEntry pcde, boo
lean move) { |
| 1113 ClassInfo classInfo = pcde.newClassInfo; |
| 1114 if (classInfo.nestedClasses == null) { |
| 1115 return Collections.emptySet(); |
| 1116 } |
| 1117 Set<String> entries = new LinkedHashSet<String>(); |
| 1118 String nestedClasses[] = classInfo.nestedClasses; |
| 1119 String javaFileFullPath = pcde.javaFileFullPath; |
| 1120 String enclosingClassFileFullPath = pcde.classFileFullPath; |
| 1121 boolean isJavaSourceFile = javaFileFullPath.endsWith(".java"); |
| 1122 |
| 1123 for (int i = 0; i < nestedClasses.length; i++) { |
| 1124 PCDEntry nestedPCDE = pcd.get(nestedClasses[i]); |
| 1125 if (nestedPCDE == null) { |
| 1126 if (isJavaSourceFile) { |
| 1127 nestedPCDE = |
| 1128 findClassFileOnFilesystem(null, pcde, nestedClasses[
i], true); |
| 1129 } |
| 1130 // For classes that come from a .jar, pcde should already be the
re. Otherwise this class just doesn't exist. |
| 1131 if (nestedPCDE == null) { |
| 1132 // Probably a compilation error, such that enclosing class i
s compiled but nested is not. |
| 1133 throw new PrivateException(new PublicExceptions.ClassNameMis
matchException( |
| 1134 "Could not find class file for " + pcde.toString()))
; |
| 1135 } |
| 1136 } |
| 1137 if (move) { |
| 1138 if (deletedClasses.contains(nestedClasses[i])) { |
| 1139 deletedClasses.remove(nestedClasses[i]); |
| 1140 } |
| 1141 nestedPCDE.javaFileFullPath = javaFileFullPath; |
| 1142 if (javaFileFullPath.endsWith(".java")) { |
| 1143 nestedPCDE.classFileFullPath = |
| 1144 Utils.getClassFileFullPathForNestedClass(enclosingCl
assFileFullPath, nestedClasses[i]); |
| 1145 } else { |
| 1146 nestedPCDE.classFileFullPath = javaFileFullPath; |
| 1147 } |
| 1148 } |
| 1149 if (nestedPCDE.newClassInfo == null) { |
| 1150 getClassInfoForPCDEntry(ClassInfo.VER_NEW, nestedPCDE); |
| 1151 } |
| 1152 nestedPCDE.newClassInfo.accessFlags = |
| 1153 pcde.newClassInfo.nestedClassAccessFlags[i]; |
| 1154 nestedPCDE.newClassInfo.isNonMemberNestedClass = |
| 1155 pcde.newClassInfo.nestedClassNonMember[i]; |
| 1156 |
| 1157 entries.add(nestedPCDE.className); |
| 1158 entries.addAll(findAndUpdateAllNestedClassesForClass(nestedPCDE, mov
e)); |
| 1159 } |
| 1160 return entries; |
| 1161 } |
| 1162 |
| 1163 /** |
| 1164 * Take care of new nested classes that could have been generated from alrea
dy existing .java sources, |
| 1165 * and of nested classes that do not exist anymore because they were deleted
from these sources. |
| 1166 */ |
| 1167 private void dealWithNestedClassesForUpdatedJavaFiles() { |
| 1168 if (updatedJavaFiles.size() == 0) { |
| 1169 return; |
| 1170 } |
| 1171 |
| 1172 // First put PCDEntries for all updated classes that have nested classes
into a temporary list. |
| 1173 // That's because we can then find new nested classes, which we will nee
d to add to the PCD, which |
| 1174 // may probably conflict with us still iterating over it. |
| 1175 List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>(); |
| 1176 for (PCDEntry pcde : entries()) { |
| 1177 if (pcde.checkResult == PCDEntry.CV_NEW) { |
| 1178 continue; // This class has just been added to the PCD |
| 1179 } |
| 1180 if (updatedJavaFiles.contains(pcde.javaFileFullPath)) { |
| 1181 ClassInfo oldClassInfo = pcde.oldClassInfo; |
| 1182 ClassInfo newClassInfo = |
| 1183 getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde); |
| 1184 if (newClassInfo == null) { |
| 1185 deletedClasses.add(pcde.className); |
| 1186 continue; // Class file deleted then not re-created due to a
compilation error somewhere. |
| 1187 } |
| 1188 if (oldClassInfo.nestedClasses != null || newClassInfo.nestedCla
sses != null) { |
| 1189 updatedEntries.add(pcde); |
| 1190 } |
| 1191 } |
| 1192 } |
| 1193 |
| 1194 if (dependencyFile != null) { |
| 1195 Map<String, List<String>> dependencies = parseDependencyFile(); |
| 1196 for (String file : updatedJavaFiles) { |
| 1197 List<String> myDeps = dependencies.get(file); |
| 1198 if (myDeps == null) |
| 1199 continue; |
| 1200 PCDEntry pcde = getNamedPCDE(file, dependencies); |
| 1201 for (String dependency : myDeps) { |
| 1202 PCDEntry dep = pcd.get(dependency); |
| 1203 if (dep != null) |
| 1204 // This is an existing dep. |
| 1205 continue; |
| 1206 dep = findClassFileOnFilesystem(file, pcde, dependency, fal
se); |
| 1207 getClassInfoForPCDEntry(ClassInfo.VER_NEW, dep); |
| 1208 if (dep.newClassInfo.nestedClasses != null) |
| 1209 updatedEntries.add(dep); |
| 1210 } |
| 1211 } |
| 1212 } |
| 1213 dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false); |
| 1214 } |
| 1215 |
| 1216 private PCDEntry getNamedPCDE(String file, Map<String, List<String>> depende
ncies) { |
| 1217 List<String> depsForFile = dependencies.get(file); |
| 1218 PCDEntry pcde = null; |
| 1219 // Find a non-nested class for this java file for which we already have |
| 1220 // a pcde |
| 1221 for (String dependency : depsForFile) { |
| 1222 if (dependency.indexOf('$') != -1) |
| 1223 continue; |
| 1224 pcde = pcd.get(dependency); |
| 1225 if (pcde != null) |
| 1226 break; |
| 1227 } |
| 1228 if (pcde == null) { |
| 1229 throw new PrivateException(new PublicExceptions.InternalException(fi
le |
| 1230 + " was supposed to be an updated file, but there are no PCD
Entries for any of its deps")); |
| 1231 } |
| 1232 return pcde; |
| 1233 } |
| 1234 |
| 1235 private void dealWithNestedClassesForUpdatedPCDEntries(List<PCDEntry> entrie
s, boolean move) { |
| 1236 for (int i = 0; i < entries.size(); i++) { |
| 1237 PCDEntry pcde = entries.get(i); |
| 1238 ClassInfo oldClassInfo = pcde.oldClassInfo; |
| 1239 ClassInfo newClassInfo = pcde.newClassInfo; |
| 1240 if (newClassInfo.nestedClasses != null) { |
| 1241 Set<String> nested = findAndUpdateAllNestedClassesForClass(pcde,
move); |
| 1242 if (oldClassInfo.nestedClasses != null) { // Check if any old n
ested classes don't exist anymore |
| 1243 for (int j = 0; j < oldClassInfo.nestedClasses.length; j++)
{ |
| 1244 boolean found = false; |
| 1245 String oldNestedClass = oldClassInfo.nestedClasses[j]; |
| 1246 for (int k = 0; k < newClassInfo.nestedClasses.length; k
++) { |
| 1247 if (oldNestedClass.equals(newClassInfo.nestedClasses
[k])) { |
| 1248 found = true; |
| 1249 break; |
| 1250 } |
| 1251 } |
| 1252 if (!found) { |
| 1253 deletedClasses.add(oldNestedClass); |
| 1254 } |
| 1255 } |
| 1256 } |
| 1257 } else { // newNestedClasses == null and oldNestedClasses != null,
so all nested classes have been removed in the new version |
| 1258 for (int j = 0; j < oldClassInfo.nestedClasses.length; j++) { |
| 1259 deletedClasses.add(oldClassInfo.nestedClasses[j]); |
| 1260 } |
| 1261 } |
| 1262 } |
| 1263 } |
| 1264 |
| 1265 private void findUpdatedClasses() { |
| 1266 // This (iterating over all of the classes once again after performing t
hat in classFileObsoleteOrDeleted()) may |
| 1267 // seem time-consuming, but in reality it isn't, since the most time-con
suming operation of obtaining internal |
| 1268 // file handles for class files has already been performed in classFileO
bsoleteOrDeleted(). Once we have done that, |
| 1269 // this re-iteration takes very small amount of time. However, if we swi
tch from "class file older than .java |
| 1270 // file" to ".java file timestamp changed" condition for recompilation,
this will have to be changed as well. |
| 1271 for (PCDEntry entry : entries()) { |
| 1272 String className = entry.className; |
| 1273 if (updatedAndCheckedClasses.contains(className) || |
| 1274 deletedClasses.contains(className)) { |
| 1275 continue; |
| 1276 } |
| 1277 if (!entry.javaFileFullPath.endsWith(".java")) { |
| 1278 continue; // classes from (updated) .jars have been dealt with s
eparately |
| 1279 } |
| 1280 //DAB TODO understand this bit better. It is needed to support -vpa
th, I'm just not sure why.... |
| 1281 if (entry.checkResult != PCDEntry.CV_NEWER_FOUND_NEARER && |
| 1282 !updatedAndCheckedClasses.contains(className) && |
| 1283 !deletedClasses.contains(className) && |
| 1284 entry.javaFileFullPath.endsWith(".java") && |
| 1285 classFileUpdated(entry)) |
| 1286 { |
| 1287 //DAB TODO this is the old way.... |
| 1288 //DAB if (classFileUpdated(entry)) { |
| 1289 updatedClasses.add(className); |
| 1290 allUpdatedClasses.add(className); |
| 1291 } |
| 1292 } |
| 1293 } |
| 1294 |
| 1295 private boolean classFileUpdated(PCDEntry entry) { |
| 1296 File classFile = Utils.checkFileForName(entry.classFileFullPath); |
| 1297 if (classFile == null) { |
| 1298 return false; |
| 1299 } |
| 1300 // The only case when the above can happen is if class file was first de
leted, and then there |
| 1301 // was an error recompiling its source |
| 1302 |
| 1303 long classFileLastMod = classFile.lastModified(); |
| 1304 |
| 1305 if (classFileLastMod > entry.oldClassFileLastModified) { |
| 1306 entry.newClassFileLastModified = classFileLastMod; |
| 1307 // Check if the class was actually modified, to avoid the costly pro
cedure of detailed version compare |
| 1308 long classFileFP = computeFP(classFile); |
| 1309 if (classFileFP != entry.oldClassFileFingerprint) { |
| 1310 entry.newClassFileFingerprint = classFileFP; |
| 1311 return true; |
| 1312 } |
| 1313 } |
| 1314 return false; |
| 1315 } |
| 1316 |
| 1317 /** |
| 1318 * Compare old (preserved in pdb) and new (file system) versions of updated
classes, and find all |
| 1319 * potentially affected dependent classes. |
| 1320 */ |
| 1321 private void checkUpdatedClasses() { |
| 1322 for (String className : updatedClasses) { |
| 1323 PCDEntry pcde = pcd.get(className); |
| 1324 getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde); |
| 1325 if (!"".equals(pcde.oldClassInfo.directlyEnclosingClass)) { |
| 1326 // The following problem can occur with nested classes. A C.java
source has been changed, so that C.class is |
| 1327 // not changed or changed in a compatible way, whereas the acces
s modifiers of C$X.class are changed in an |
| 1328 // incompatible way, so that something is broken in the project.
When jmake is called for the first time, |
| 1329 // it reports the problem, then saves the info on the new versio
n of C in the pdb. Of course, the record for |
| 1330 // C$X in the pdb is not updated, since the change to it is inco
mpatible and recompilation of dependent sources |
| 1331 // has failed. Suppose we don't change anything and invoke jmake
again. C$X is found different from its old |
| 1332 // version and is checked here again. The outcome should be the
same. But since C has not changed, C.class is |
| 1333 // not read from disk and the access flags of C$X, which are sto
red in C.class, are not set appropriately. So |
| 1334 // in such circumstances we have wrong access flags for C$X here
. To fix the problem we need to load C explicitly. |
| 1335 ClassInfo enclosingClassInfo = |
| 1336 getClassInfoForName(ClassInfo.VER_NEW, pcde.oldClassInfo
.directlyEnclosingClass); |
| 1337 //if (enclosingClassInfo == null || enclosingClassInfo.nestedCla
sses == null) { |
| 1338 // System.out.println("!!! Suspicious updated class name = " +
className); |
| 1339 // System.out.println("!!! enclosingClassInfo for it = " + encl
osingClassInfo); |
| 1340 // if (enclosingClassInfo != null) { |
| 1341 // System.out.println("!!! enclosingClassInfo.name = " + encl
osingClassInfo.name); |
| 1342 // if (enclosingClassInfo.nestedClasses == null) System.out.p
rintln("!!! enclosingClassInfo.nestedClasses = null"); |
| 1343 // } |
| 1344 //} |
| 1345 if (enclosingClassInfo.nestedClasses != null) { // Can be that
this nested class was the only one for enclosing class, and it's deleted now |
| 1346 for (int i = 0; i < enclosingClassInfo.nestedClasses.length;
i++) { |
| 1347 if (className.equals(enclosingClassInfo.nestedClasses[i]
)) { |
| 1348 pcde.newClassInfo.accessFlags = |
| 1349 enclosingClassInfo.nestedClassAccessFlags[i]
; |
| 1350 pcde.newClassInfo.isNonMemberNestedClass = |
| 1351 enclosingClassInfo.nestedClassNonMember[i]; |
| 1352 break; |
| 1353 } |
| 1354 } |
| 1355 } |
| 1356 } |
| 1357 if (!(pcde.oldClassInfo.isNonMemberNestedClass && pcde.newClassInfo.
isNonMemberNestedClass)) { |
| 1358 Utils.printInfoMessage("Checking " + pcde.className); |
| 1359 pcde.checkResult = cv.compareClassVersions(pcde) ? PCDEntry.CV_C
OMPATIBLE |
| 1360 : PCDEntry.CV_INCOMPATIBLE; |
| 1361 String affectedClasses[] = cv.getAffectedClasses(); |
| 1362 if (affectedClasses != null) { |
| 1363 for (int i = 0; i < affectedClasses.length; i++) { |
| 1364 PCDEntry affEntry = pcd.get(affectedClasses[i]); |
| 1365 updatedJavaFiles.add(affEntry.javaFileFullPath); |
| 1366 } |
| 1367 } |
| 1368 } else { |
| 1369 // A non-member nested class can not be referenced by the source
code of any class defined outside the |
| 1370 // immediately enclosing source code block for this class. There
fore, any incompatibility in the new |
| 1371 // version of this class can affect only classes that are define
d in the same source file - and they |
| 1372 // are necessarily recompiled together with this class. So there
is no point in initiating version |
| 1373 // compare for this class. However, the new class version should
always tembe promoted into the store, since |
| 1374 // this class itself may depend on other changing classes. |
| 1375 pcde.checkResult = PCDEntry.CV_COMPATIBLE; |
| 1376 } |
| 1377 |
| 1378 updatedAndCheckedClasses.add(className); |
| 1379 } |
| 1380 } |
| 1381 |
| 1382 /** Find all dependent classes for deleted classes. */ |
| 1383 private void checkDeletedClasses() { |
| 1384 for (String className : deletedClasses) { |
| 1385 PCDEntry pcde = pcd.get(className); |
| 1386 |
| 1387 if (pcde == null) { // "Safety net" for the (hopefully unlikely) ca
se. I observed it just once and couldn't identify the reason |
| 1388 Utils.printWarningMessage("Warning: internal information inconsi
stency when checking deleted classes"); |
| 1389 Utils.printWarningMessage(Utils.REPORT_PROBLEM); |
| 1390 Utils.printWarningMessage("Class name: " + className); |
| 1391 continue; |
| 1392 } |
| 1393 |
| 1394 ClassInfo oldCI = pcde.oldClassInfo; |
| 1395 if (!oldCI.isNonMemberNestedClass) { // See the comment above |
| 1396 Utils.printInfoMessage("Checking deleted class " + oldCI.name); |
| 1397 cv.checkDeletedClass(pcde); |
| 1398 String[] affectedClasses = cv.getAffectedClasses(); |
| 1399 if (affectedClasses != null) { |
| 1400 for (int i = 0; i < affectedClasses.length; i++) { |
| 1401 PCDEntry affEntry = pcd.get(affectedClasses[i]); |
| 1402 if (deletedClasses.contains(affEntry.className)) { |
| 1403 continue; |
| 1404 } |
| 1405 updatedJavaFiles.add(affEntry.javaFileFullPath); |
| 1406 } |
| 1407 } |
| 1408 } |
| 1409 pcde.checkResult = PCDEntry.CV_DELETED; |
| 1410 updatedAndCheckedClasses.add(className); |
| 1411 } |
| 1412 deletedClasses.clear(); |
| 1413 } |
| 1414 |
| 1415 /** |
| 1416 * Determine what classes in the given .jar (which may be an existing update
d one, or a new one) are new, |
| 1417 * updated, or moved, and treat them accordingly. |
| 1418 */ |
| 1419 private void processAllClassesFromJarFile(String jarFileName) { |
| 1420 JarFile jarFile; |
| 1421 long jarFileLastMod = 0; |
| 1422 try { |
| 1423 File file = new File(jarFileName); |
| 1424 jarFileLastMod = file.lastModified(); |
| 1425 jarFile = new JarFile(jarFileName); |
| 1426 } catch (IOException ex) { |
| 1427 throw new PrivateException(ex); |
| 1428 } |
| 1429 |
| 1430 List<PCDEntry> newEntries = new ArrayList<PCDEntry>(); |
| 1431 List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>(); |
| 1432 List<PCDEntry> movedEntries = new ArrayList<PCDEntry>(); |
| 1433 |
| 1434 for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreE
lements();) { |
| 1435 JarEntry jarEntry = entries.nextElement(); |
| 1436 String fullClassName = jarEntry.getName(); |
| 1437 if (!fullClassName.endsWith(".class")) { |
| 1438 continue; |
| 1439 } |
| 1440 fullClassName = |
| 1441 fullClassName.substring(0, fullClassName.length() - 6).inter
n(); |
| 1442 byte classFileBytes[]; |
| 1443 classFileBytes = Utils.readZipEntryIntoBuffer(jarFile, jarEntry); |
| 1444 long classFileFP = computeFP(classFileBytes); |
| 1445 |
| 1446 PCDEntry pcde = pcd.get(fullClassName); |
| 1447 if (pcde != null) { |
| 1448 if (pcde.checked) { |
| 1449 throw new PrivateException(new PublicExceptions.DoubleEntryE
xception( |
| 1450 "Two entries for class " + fullClassName + " detecte
d: " + pcde.javaFileFullPath + " and " + jarFileName)); |
| 1451 } |
| 1452 pcde.checked = true; |
| 1453 pcde.newClassFileLastModified = jarFileLastMod; |
| 1454 // If we are scanning an existing updated .jar file, and there i
s no change to the class itself, |
| 1455 // and it previously was located in the same .jar, do nothing. |
| 1456 if (pcde.oldClassFileFingerprint == classFileFP && |
| 1457 pcde.javaFileFullPath.equals(jarFileName)) { |
| 1458 pcde.oldClassFileLastModified = jarFileLastMod; // So that
next time jmake is inoked, checking |
| 1459 continue; // of this
.jar is not triggered. |
| 1460 } |
| 1461 if (pcde.oldClassFileFingerprint != classFileFP) { // This clas
s has been updated |
| 1462 updatedClasses.add(fullClassName); |
| 1463 allUpdatedClasses.add(fullClassName); |
| 1464 pcde.newClassFileLastModified = jarFileLastMod; |
| 1465 pcde.newClassFileFingerprint = classFileFP; |
| 1466 pcde.newClassInfo = |
| 1467 new ClassInfo(classFileBytes, ClassInfo.VER_NEW, thi
s, fullClassName); |
| 1468 if (pcde.oldClassInfo.nestedClasses != null || pcde.newClass
Info.nestedClasses != null) { |
| 1469 updatedEntries.add(pcde); |
| 1470 } |
| 1471 } else { |
| 1472 pcde.oldClassFileLastModified = jarFileLastMod; |
| 1473 } |
| 1474 if (!pcde.javaFileFullPath.equals(jarFileName)) { |
| 1475 // Found an existing class in a different .jar file. |
| 1476 // May happen if the class file has been moved from one .jar
to another (or into a .jar, losing its |
| 1477 // .java source). It's only at this point that we can actual
ly see that it was really a move. |
| 1478 if (deletedClasses.contains(fullClassName)) { |
| 1479 deletedClasses.remove(fullClassName); |
| 1480 } |
| 1481 if (pcde.oldClassInfo.nestedClasses != null) { |
| 1482 movedEntries.add(pcde); |
| 1483 pcde.newClassInfo = |
| 1484 new ClassInfo(classFileBytes, ClassInfo.VER_NEW,
this, fullClassName); |
| 1485 } |
| 1486 } |
| 1487 pcde.javaFileFullPath = jarFileName; |
| 1488 } else { // New class file |
| 1489 ClassInfo classInfo = |
| 1490 new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, f
ullClassName); |
| 1491 pcde = new PCDEntry(fullClassName, |
| 1492 jarFileName, |
| 1493 jarFileName, jarFileLastMod, classFileFP, |
| 1494 classInfo); |
| 1495 pcde.checkResult = PCDEntry.CV_NEW; // So that later it
's promoted into oldClassInfo correctly |
| 1496 updatedAndCheckedClasses.add(fullClassName); // So that the abov
e happens |
| 1497 pcd.put(fullClassName, pcde); |
| 1498 if (pcde.newClassInfo.nestedClasses != null) { |
| 1499 newEntries.add(pcde); |
| 1500 } |
| 1501 } |
| 1502 } |
| 1503 |
| 1504 dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false); |
| 1505 dealWithNestedClassesForUpdatedPCDEntries(movedEntries, true); |
| 1506 for (int i = 0; i < newEntries.size(); i++) { |
| 1507 findAndUpdateAllNestedClassesForClass(newEntries.get(i), false); |
| 1508 } |
| 1509 } |
| 1510 |
| 1511 /** Determine new, deleted and updated classes coming from updated .jar file
s. */ |
| 1512 private void dealWithClassesInUpdatedJarFiles() { |
| 1513 if (updatedJarFiles.size() == 0) { |
| 1514 return; |
| 1515 } |
| 1516 |
| 1517 for (String updatedJarFile : updatedJarFiles) { |
| 1518 processAllClassesFromJarFile(updatedJarFile); |
| 1519 } |
| 1520 |
| 1521 // Now scan the PCD to check which classes that come from updated .jar f
iles have not been marked as checked |
| 1522 for (PCDEntry pcde : entries()) { |
| 1523 if (updatedJarFiles.contains(pcde.javaFileFullPath)) { |
| 1524 if (!pcde.checked) { |
| 1525 deletedClasses.add(pcde.className); |
| 1526 } |
| 1527 } |
| 1528 } |
| 1529 } |
| 1530 |
| 1531 /** Check if the destination directory exists, and get the canonical path fo
r it. */ |
| 1532 private void initializeDestDir(String inDestDir) { |
| 1533 if (!(inDestDir == null || inDestDir.equals(""))) { |
| 1534 File dir = Utils.checkOrCreateDirForName(inDestDir); |
| 1535 if (dir == null) { |
| 1536 throw new PrivateException(new IOException("specified directory
" + inDestDir + " cannot be created.")); |
| 1537 } |
| 1538 inDestDir = getCanonicalPath(dir); |
| 1539 if (!inDestDir.endsWith(File.separator)) { |
| 1540 inDestDir += File.separatorChar; |
| 1541 } |
| 1542 destDir = inDestDir; |
| 1543 destDirSpecified = true; |
| 1544 } else { |
| 1545 destDirSpecified = false; |
| 1546 } |
| 1547 } |
| 1548 |
| 1549 /** |
| 1550 * For the given PCDEntry, set the entry.classFileFullPath according to the
value of the .java file full |
| 1551 * path and the value of the "-d" option at this particular jmake invocation |
| 1552 */ |
| 1553 private void initializeClassFileFullPath(PCDEntry entry) { |
| 1554 String classFileFullPath; |
| 1555 if (destDirSpecified) { |
| 1556 classFileFullPath = destDir + entry.className + ".class"; |
| 1557 } else { |
| 1558 String javaFileDir = entry.javaFileFullPath; |
| 1559 int cutIndex = javaFileDir.lastIndexOf(File.separatorChar); |
| 1560 if (cutIndex != -1) { |
| 1561 javaFileDir = javaFileDir.substring(0, cutIndex + 1); |
| 1562 } |
| 1563 String classFileName = entry.className; |
| 1564 cutIndex = classFileName.lastIndexOf('/'); |
| 1565 if (cutIndex != -1) { |
| 1566 classFileName = classFileName.substring(cutIndex + 1); |
| 1567 } |
| 1568 classFileFullPath = javaFileDir + classFileName + ".class"; |
| 1569 } |
| 1570 if (backSlashFileSeparator) { |
| 1571 classFileFullPath = |
| 1572 classFileFullPath.replace('/', File.separatorChar); |
| 1573 } |
| 1574 entry.classFileFullPath = classFileFullPath; |
| 1575 } |
| 1576 |
| 1577 private static String getCanonicalPath(File file) { |
| 1578 try { |
| 1579 return file.getCanonicalPath().intern(); |
| 1580 } catch (IOException e) { |
| 1581 throw new PrivateException(e); |
| 1582 } |
| 1583 } |
| 1584 |
| 1585 private long computeFP(File file) { |
| 1586 byte buf[] = Utils.readFileIntoBuffer(file); |
| 1587 return computeFP(buf); |
| 1588 } |
| 1589 |
| 1590 private long computeFP(byte[] buf) { |
| 1591 checkSum.reset(); |
| 1592 checkSum.update(buf); |
| 1593 return checkSum.getValue(); |
| 1594 } |
| 1595 |
| 1596 private PrivateException compilerInteractionException(String message, Except
ion origException, int errCode) { |
| 1597 return new PrivateException(new PublicExceptions.CompilerInteractionExce
ption(message, origException, errCode)); |
| 1598 } |
| 1599 |
| 1600 private PrivateException internalException(String message) { |
| 1601 return new PrivateException(new PublicExceptions.InternalException(messa
ge)); |
| 1602 } |
| 1603 } |
OLD | NEW |