Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(688)

Side by Side Diff: third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java

Issue 1373723003: Fix javac --incremental by using jmake for dependency analysis (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@aidl
Patch Set: fix license check Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698