| Index: third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java
|
| diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java b/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5ff3cd10a95c18b8597a781c0010ab408206b6f1
|
| --- /dev/null
|
| +++ b/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java
|
| @@ -0,0 +1,1603 @@
|
| +/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
|
| + *
|
| + * This program is distributed under the terms of
|
| + * the GNU General Public License Version 2. See the LICENSE file
|
| + * at the top of the source tree.
|
| + */
|
| +package org.pantsbuild.jmake;
|
| +
|
| +import java.io.BufferedReader;
|
| +import java.io.File;
|
| +import java.io.FileNotFoundException;
|
| +import java.io.FileReader;
|
| +import java.io.IOException;
|
| +import java.io.InputStream;
|
| +import java.lang.reflect.InvocationTargetException;
|
| +import java.lang.reflect.Method;
|
| +import java.util.ArrayList;
|
| +import java.util.Collection;
|
| +import java.util.Collections;
|
| +import java.util.Enumeration;
|
| +import java.util.HashMap;
|
| +import java.util.HashSet;
|
| +import java.util.LinkedHashMap;
|
| +import java.util.LinkedHashSet;
|
| +import java.util.List;
|
| +import java.util.Map;
|
| +import java.util.Map.Entry;
|
| +import java.util.Set;
|
| +import java.util.StringTokenizer;
|
| +import java.util.jar.JarEntry;
|
| +import java.util.jar.JarFile;
|
| +import java.util.zip.Adler32;
|
| +
|
| +/**
|
| + * This class implements management of the Project Class Directory, automatic tracking
|
| + * of changes and recompilation of .java sources for a project.
|
| + *
|
| + * @author Misha Dmitriev
|
| + * 23 January 2003
|
| + */
|
| +public class PCDManager {
|
| +
|
| + private PCDContainer pcdc;
|
| + private Map<String,PCDEntry> pcd; // Maps project class names to PCDEntries
|
| + private String projectJavaAndJarFilesArray[];
|
| + private String addedJavaAndJarFilesArray[], removedJavaAndJarFilesArray[], updatedJavaAndJarFilesArray[];
|
| + private List<String> newJavaFiles;
|
| + private Set<String> updatedJavaFiles;
|
| + private Set<String> recompiledJavaFiles;
|
| + private Set<String> updatedClasses; // This set is emptied on every new internal jmake iteration...
|
| + private Set<String> allUpdatedClasses; // whereas in this one the names of all updated classes found during this jmake invocation are stored.
|
| + private Set<String> updatedAndCheckedClasses;
|
| + private Set<String> deletedClasses;
|
| + private Set<String> updatedJarFiles;
|
| + private Set<String> stableJarFiles;
|
| + private Set<String> newJarFiles;
|
| + private Set<String> deletedJarFiles;
|
| + /* Dependencies from the dependencyFile, if any */
|
| + private Map<String, List<String>> extraDependencies;
|
| +
|
| + private String destDir;
|
| + private boolean destDirSpecified;
|
| + private List<String> javacAddArgs;
|
| + private Class<?> compilerClass;
|
| + private Method compileMethod;
|
| + private String jcExecApp;
|
| + private Object externalApp;
|
| + private Method externalCompileSourceFilesMethod;
|
| + private Adler32 checkSum;
|
| + private CompatibilityChecker cv;
|
| + private ClassFileReader cfr;
|
| + private boolean newProject = false;
|
| + private String dependencyFile = null;
|
| + private static boolean backSlashFileSeparator = File.separatorChar != '/';
|
| +
|
| + /**** Interface to the class ****/
|
| + /**
|
| + * Either projectJavaAndJarFilesArray != null and added.. == removed.. == updatedJavaAndJarFilesArray == null,
|
| + * or projectJavaAndJarFilesArray == null and one or more of others != null.
|
| + * When PCDManager is called from Main, this is guaranteed, since separate entrypoint functions initialize
|
| + * either one or another of the above argument groups, but never both.
|
| + */
|
| + public PCDManager(PCDContainer pcdc,
|
| + String projectJavaAndJarFilesArray[],
|
| + String addedJavaAndJarFilesArray[],
|
| + String removedJavaAndJarFilesArray[],
|
| + String updatedJavaAndJarFilesArray[],
|
| + String in_destDir,
|
| + List<String> javacAddArgs,
|
| + boolean failOnDependentJar,
|
| + boolean noWarnOnDependentJar,
|
| + String dependencyFile) {
|
| + this.pcdc = pcdc;
|
| + if (pcdc.pcd == null) {
|
| + pcd = new LinkedHashMap<String,PCDEntry>();
|
| + pcdc.pcd = pcd;
|
| + newProject = true;
|
| + } else {
|
| + pcd = pcdc.pcd;
|
| + }
|
| +
|
| + this.projectJavaAndJarFilesArray = projectJavaAndJarFilesArray;
|
| + this.addedJavaAndJarFilesArray = addedJavaAndJarFilesArray;
|
| + this.removedJavaAndJarFilesArray = removedJavaAndJarFilesArray;
|
| + this.updatedJavaAndJarFilesArray = updatedJavaAndJarFilesArray;
|
| + this.dependencyFile = dependencyFile;
|
| + newJavaFiles = new ArrayList<String>();
|
| + updatedJavaFiles = new LinkedHashSet<String>();
|
| + recompiledJavaFiles = new LinkedHashSet<String>();
|
| + updatedAndCheckedClasses = new LinkedHashSet<String>();
|
| + deletedClasses = new LinkedHashSet<String>();
|
| + allUpdatedClasses = new LinkedHashSet<String>();
|
| +
|
| + updatedJarFiles = new LinkedHashSet<String>();
|
| + stableJarFiles = new LinkedHashSet<String>();
|
| + newJarFiles = new LinkedHashSet<String>();
|
| + deletedJarFiles = new LinkedHashSet<String>();
|
| +
|
| + initializeDestDir(in_destDir);
|
| + this.javacAddArgs = javacAddArgs;
|
| +
|
| + checkSum = new Adler32();
|
| +
|
| + cv = new CompatibilityChecker(this, failOnDependentJar, noWarnOnDependentJar);
|
| + cfr = new ClassFileReader();
|
| + }
|
| +
|
| + public Collection<PCDEntry> entries() {
|
| + return pcd.values();
|
| + }
|
| +
|
| + public ClassFileReader getClassFileReader() {
|
| + return cfr;
|
| + }
|
| +
|
| + public ClassInfo getClassInfoForName(int verCode, String className) {
|
| + PCDEntry pcde = pcd.get(className);
|
| + if (pcde != null) {
|
| + return getClassInfoForPCDEntry(verCode, pcde);
|
| + } else {
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + public boolean isProjectClass(int verCode, String className) {
|
| + if (verCode == ClassInfo.VER_OLD) {
|
| + return pcd.containsKey(className);
|
| + } else {
|
| + PCDEntry pcde = pcd.get(className);
|
| + return (pcde != null && pcde.checkResult != PCDEntry.CV_DELETED);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Get an instance of ClassInfo (load a class file if necessary) for the given version (old or new) of
|
| + * the class determined by pcde. For an old class version, always returns a non-null result; but for a new
|
| + * version, null is returned if class file is not found. In most of the current uses of this method null result
|
| + * is not checked, because it's either called for an old version or it is already known that the .class file
|
| + * should be present; nevertheless, beware!
|
| + */
|
| + public ClassInfo getClassInfoForPCDEntry(int verCode, PCDEntry pcde) {
|
| + if (verCode == ClassInfo.VER_OLD) {
|
| + return pcde.oldClassInfo;
|
| + }
|
| +
|
| + ClassInfo res = pcde.newClassInfo;
|
| + if (res == null) {
|
| + byte classFileBytes[];
|
| + String classFileFullPath = null;
|
| + if (pcde.javaFileFullPath.endsWith(".java")) {
|
| + File classFile = Utils.checkFileForName(pcde.classFileFullPath);
|
| + if (classFile == null) {
|
| + return null; // Class file not found.
|
| + }
|
| + classFileBytes = Utils.readFileIntoBuffer(classFile);
|
| + classFileFullPath = pcde.classFileFullPath;
|
| + } else {
|
| + try {
|
| + JarFile jarFile = new JarFile(pcde.javaFileFullPath);
|
| + JarEntry jarEntry =
|
| + jarFile.getJarEntry(pcde.className + ".class");
|
| + if (jarEntry == null) {
|
| + return null;
|
| + }
|
| + classFileBytes =
|
| + Utils.readZipEntryIntoBuffer(jarFile, jarEntry);
|
| + } catch (IOException ex) {
|
| + throw new PrivateException(ex);
|
| + }
|
| + }
|
| + res =
|
| + new ClassInfo(classFileBytes, verCode, this, classFileFullPath);
|
| + pcde.newClassInfo = res;
|
| + }
|
| + return res;
|
| + }
|
| +
|
| + /**
|
| + * Returns null if class is compileable (has a .java source) and not recompiled yet, "" if
|
| + * class has already been recompiled or has been deleted from project, and the class's .jar
|
| + * name if class comes from a jar, hence is uncompileable.
|
| + */
|
| + public String classAlreadyRecompiledOrUncompileable(String className) {
|
| + PCDEntry pcde = pcd.get(className);
|
| + if (pcde == null) {
|
| + //!!!
|
| + for (String keyName : pcd.keySet()) {
|
| + PCDEntry entry = pcd.get(keyName);
|
| + if (entry.className.equals(className)) {
|
| + System.out.println("ERROR: inconsistent entry: key = " +
|
| + keyName + ", name in entry = " + entry.className);
|
| + }
|
| + }
|
| + //!!!
|
| + throw internalException(className + " not in project when it should be");
|
| + }
|
| + if (pcde.checkResult == PCDEntry.CV_DELETED) {
|
| + return "";
|
| + }
|
| + if (pcde.javaFileFullPath.endsWith(".jar")) {
|
| + return pcde.javaFileFullPath;
|
| + } else {
|
| + return (recompiledJavaFiles.contains(pcde.javaFileFullPath) ? "" : null);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Compiler initialization depends on compiler type specified.
|
| + * If jcExecApp != null, i.e. an external executable compiler application is used, and nothing has to be done.
|
| + * If externalApp != null, that is, jmake is called by an external application such as Ant, which
|
| + * manages compilation in its own way, and also nothing has to be done.
|
| + * Otherwise, load the compiler class and method (either specified through jcPath, jcMainClass and jcMethod,
|
| + * or the default one.
|
| + */
|
| + public void initializeCompiler(String jcExecApp,
|
| + String jcPath, String jcMainClass, String jcMethod,
|
| + Object externalApp, Method externalCompileSourceFilesMethod) {
|
| + ClassPath.initializeAllClassPaths();
|
| +
|
| + if (externalApp != null) {
|
| + this.externalApp = externalApp;
|
| + this.externalCompileSourceFilesMethod =
|
| + externalCompileSourceFilesMethod;
|
| + return;
|
| + }
|
| + if (jcExecApp != null) {
|
| + this.jcExecApp = jcExecApp;
|
| + return;
|
| + }
|
| +
|
| + if (jcPath == null) {
|
| + String javaHome = System.getProperty("java.home");
|
| + // 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
|
| + // this latter directory.
|
| + if (javaHome.endsWith(File.separator + "jre") || javaHome.endsWith(File.separator + "bin")) {
|
| + javaHome = javaHome.substring(0, javaHome.length() - 4);
|
| + }
|
| + jcPath = javaHome + "/lib/tools.jar";
|
| + }
|
| + ClassLoader compilerLoader;
|
| + try {
|
| + compilerLoader = ClassPath.getClassLoaderForPath(jcPath);
|
| + } catch (Exception ex) {
|
| + throw compilerInteractionException("error opening compiler path", ex, 0);
|
| + }
|
| +
|
| + if (jcMainClass == null) {
|
| + jcMainClass = "com.sun.tools.javac.Main";
|
| + }
|
| + if (jcMethod == null) {
|
| + jcMethod = "compile";
|
| + }
|
| +
|
| + try {
|
| + compilerClass = compilerLoader.loadClass(jcMainClass);
|
| + } catch (ClassNotFoundException e) {
|
| + throw compilerInteractionException("error loading compiler main class " + jcMainClass, e, 0);
|
| + }
|
| +
|
| + Class<?>[] args = new Class<?>[]{String[].class};
|
| + try {
|
| + compileMethod = compilerClass.getMethod(jcMethod, args);
|
| + } catch (Exception e) {
|
| + throw compilerInteractionException("error getting method com.sun.tools.javac.Main.compile(String args[])", e, 0);
|
| + }
|
| + }
|
| +
|
| + /** Main entrypoint for this class */
|
| + public void run() {
|
| + Utils.startTiming(Utils.TIMING_SYNCHRO);
|
| + synchronizeProjectFilesAndPCD();
|
| + Utils.stopAndPrintTiming("Synchro", Utils.TIMING_SYNCHRO);
|
| + Utils.printTiming("of which synchro check file", Utils.TIMING_SYNCHRO_CHECK_JAVA_FILES);
|
| +
|
| + Utils.startTiming(Utils.TIMING_FIND_UPDATED_JAVA_FILES);
|
| + findUpdatedJavaAndJarFiles();
|
| + Utils.stopAndPrintTiming("findUpdatedJavaAndJarFiles", Utils.TIMING_FIND_UPDATED_JAVA_FILES);
|
| + Utils.printTiming("of which classFileObsoleteOrDeleted", Utils.TIMING_CLASS_FILE_OBSOLETE_OR_DELETED);
|
| +
|
| + // Let's free some memory
|
| + projectJavaAndJarFilesArray = null;
|
| +
|
| + updatedClasses = new LinkedHashSet<String>();
|
| + dealWithClassesInUpdatedJarFiles();
|
| +
|
| + int iterNo = 0;
|
| + int res = 0;
|
| + while (iterNo == 0 || updatedJavaFiles.size() != 0 || newJavaFiles.size() != 0) {
|
| + // It may happen that we didn't find any updated or new .java files. However, we still need to enter
|
| + // this loop because there may be some class files that need compatibility checking. This can happen
|
| + // either if somebody had recompiled their sources bypassing jmake, or if their checking during the
|
| + // previous invocation of jmake failed, because their dependent code recompilation failed.
|
| + if (updatedJavaFiles.size() > 0 || newJavaFiles.size() > 0) {
|
| + Utils.startTiming(Utils.TIMING_COMPILE);
|
| + int intermediateRes = recompileUpdatedJavaFiles();
|
| + Utils.stopAndPrintTiming("Compile", Utils.TIMING_COMPILE);
|
| + if (intermediateRes != 0) {
|
| + res = intermediateRes;
|
| + }
|
| + }
|
| +
|
| + Utils.startTiming(Utils.TIMING_PDBUPDATE);
|
| + // New classes can be added to pdb only if compilation was successful, i.e. the new project version is consistent.
|
| + if (iterNo++ == 0 && res == 0) {
|
| + findClassFilesForNewJavaAndJarFiles();
|
| + findClassFilesForUpdatedJavaFiles();
|
| + dealWithNestedClassesForUpdatedJavaFiles();
|
| + }
|
| + Utils.stopAndPrintTiming("Entering new classes in PDB", Utils.TIMING_PDBUPDATE);
|
| +
|
| + updatedJavaFiles.clear();
|
| + newJavaFiles.clear();
|
| +
|
| + Utils.startTiming(Utils.TIMING_FIND_UPDATED_CLASSES);
|
| + findUpdatedClasses();
|
| + Utils.stopAndPrintTiming("Find updated classes", Utils.TIMING_FIND_UPDATED_CLASSES);
|
| +
|
| + Utils.startTiming(Utils.TIMING_CHECK_UPDATED_CLASSES);
|
| + checkDeletedClasses();
|
| + checkUpdatedClasses();
|
| + Utils.stopAndPrintTiming("Check updated classes", Utils.TIMING_CHECK_UPDATED_CLASSES);
|
| +
|
| + updatedClasses = new LinkedHashSet<String>();
|
| + if (ClassPath.getVirtualPath() != null) {
|
| + if (res != 0)
|
| + break;
|
| + }
|
| + }
|
| +
|
| + Utils.startTiming(Utils.TIMING_PDBWRITE);
|
| + updateClassFilesInfoInPCD(res);
|
| + pcdc.save();
|
| + Utils.stopAndPrintTiming("PDB write", Utils.TIMING_PDBWRITE);
|
| +
|
| + if (res != 0) {
|
| + throw compilerInteractionException("compilation error(s)", null, res);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Find the newly-created class files for existing java files.
|
| + */
|
| + private void findClassFilesForUpdatedJavaFiles() {
|
| + if (dependencyFile == null)
|
| + return;
|
| +
|
| + Set<String> allClasses = new HashSet<String>();
|
| +
|
| + Map<String, List<String>> dependencies = parseDependencyFile();
|
| + for (String file : updatedJavaFiles) {
|
| + List<String> myDeps = dependencies.get(file);
|
| + if (myDeps != null) {
|
| + PCDEntry parent = getNamedPCDE(file, dependencies);
|
| + for (String dependency : myDeps) {
|
| + allClasses.add(dependency);
|
| + if (pcd.containsKey(dependency))
|
| + continue;
|
| + findClassFileOnFilesystem(file, parent, dependency, false);
|
| + }
|
| + }
|
| + }
|
| + for (Map.Entry<String, PCDEntry> entry : pcd.entrySet()) {
|
| + String cls = entry.getKey();
|
| + if (!allClasses.contains(cls)) {
|
| + PCDEntry pcde = entry.getValue();
|
| + if (updatedJavaFiles.contains(pcde.javaFileFullPath)) {
|
| + deletedClasses.add(cls);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + public String[] getAllUpdatedClassesAsStringArray() {
|
| + String[] res = new String[allUpdatedClasses.size()];
|
| + int i = 0;
|
| + for (String updatedClass : allUpdatedClasses) {
|
| + res[i++] = updatedClass.replace('/', '.');
|
| + }
|
| + return res;
|
| + }
|
| +
|
| + /**
|
| + * Synchronize projectJavaAndJarFilesArray and PCD, i.e. leave only those entries in the PCD which have their
|
| + * .java (.jar) files in projectJavaAndJarFilesArray. New .java files in projectJavaAndJarFilesArray (i.e. those
|
| + * for which there are no entries in the PCD yet) are added to newJavaFiles; new .jar files are added to newJarFiles.
|
| + * Alternatively, just use the supplied arrays of added and deleted .java and .jar files.
|
| + *
|
| + * For entries whose .java files are not in the PCD anymore, try to delete .class files. We need to do that before
|
| + * compilation to avoid the situation when a .java file is removed but compilation succeeds because the .class file
|
| + * is still there.
|
| + *
|
| + * Unfortunately, we also need to delete all class files for non-nested classes whose names differ from their .java
|
| + * file name, because we can't tell when they've been removed from their .java files -- but it's only safe to do this
|
| + * for files that originate from java files that we're compiling this round.
|
| + *
|
| + * Upon return from this method, all of the .java and .jar files in the PCD are known to exist.
|
| + */
|
| + private void synchronizeProjectFilesAndPCD() {
|
| + if (projectJavaAndJarFilesArray != null) {
|
| + Set<String> pcdJavaFilesSet = new LinkedHashSet<String>(pcd.size() * 3 / 2);
|
| + for(PCDEntry entry : entries()) {
|
| + pcdJavaFilesSet.add(entry.javaFileFullPath);
|
| + }
|
| +
|
| + Set<String> canonicalPJF =
|
| + new LinkedHashSet<String>(projectJavaAndJarFilesArray.length * 3 / 2);
|
| +
|
| + // Add .java files that are not in PCD to newJavaFiles; add .jar files that are not in PCD to newJarFiles.
|
| + for (int i = 0; i < projectJavaAndJarFilesArray.length; i++) {
|
| + String projFileName = projectJavaAndJarFilesArray[i];
|
| + Utils.startTiming(Utils.TIMING_SYNCHRO_CHECK_TMP);
|
| + File projFile = Utils.checkFileForName(projFileName);
|
| + Utils.stopAndAddTiming(Utils.TIMING_SYNCHRO_CHECK_TMP, Utils.TIMING_SYNCHRO_CHECK_JAVA_FILES);
|
| + if (projFile == null) {
|
| + throw new PrivateException(new FileNotFoundException("specified source file " + projFileName + " not found."));
|
| + }
|
| + // The main reason for using getAbsolutePath() instead of more reliable getCanonicalPath() is the fact that
|
| + // sometimes users may name the actual files containing Java code in some custom way, and give javac/jmake
|
| + // symbolic links to these files (that have correct .java names) instead. getCanonicalPath(), however, returns the
|
| + // real (i.e. user custom) file name, which will confuse our test below and then javac.
|
| + String absoluteProjFileName = projFile.getAbsolutePath();
|
| + // On Windows, make sure the drive letter is always in lower case
|
| + if (backSlashFileSeparator) {
|
| + absoluteProjFileName =
|
| + Utils.convertDriveLetterToLowerCase(absoluteProjFileName);
|
| + }
|
| + canonicalPJF.add(absoluteProjFileName);
|
| + if (!pcdJavaFilesSet.contains(absoluteProjFileName)) {
|
| + if (absoluteProjFileName.endsWith(".java")) {
|
| + newJavaFiles.add(absoluteProjFileName);
|
| + } else if (absoluteProjFileName.endsWith(".jar")) {
|
| + newJarFiles.add(absoluteProjFileName);
|
| + } else {
|
| + throw new PrivateException(new PublicExceptions.InvalidSourceFileExtensionException("specified source file " + projFileName + " has an invalid extension (not .java or .jar)."));
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Find the entries containing .java or .jar files that are not in project anymore
|
| + for (Entry<String, PCDEntry> entry : pcd.entrySet()) {
|
| + String key = entry.getKey();
|
| + PCDEntry e = entry.getValue();
|
| + e.oldClassInfo.restorePCDM(this);
|
| + if (canonicalPJF.contains(e.javaFileFullPath)) {
|
| + if (e.isPackagePrivateClass()) {
|
| + initializeClassFileFullPath(e);
|
| + new File(e.classFileFullPath).delete();
|
| + }
|
| + } else {
|
| + if (ClassPath.getVirtualPath() == null) {
|
| + deletedClasses.add(key);
|
| + } else {
|
| + // Okay, not found locally, but virtual path was defined, so try it now....
|
| + if ( (e.oldClassFileFingerprint == projectJavaAndJarFilesArray.length &&
|
| + newJavaFiles.size() == 0) ||
|
| + Utils.checkFileForName(e.javaFileFullPath) != null)
|
| + {
|
| + e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER;
|
| + e.oldClassFileFingerprint = projectJavaAndJarFilesArray.length;
|
| + }
|
| + else
|
| + {
|
| + String classFound = null;
|
| + String sourceFound = null;
|
| + // Find source and class file via virtual path
|
| + String path = ClassPath.getVirtualPath();
|
| + // TODO(Eric Ayers): IntelliJ static analysis shows several useless
|
| + // expressions that make this loop a no-op.
|
| + for (StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
|
| + !(classFound != null && sourceFound != null) && st.hasMoreTokens();)
|
| + {
|
| + String fullPath = st.nextToken()+File.separator+e.className;
|
| + if (sourceFound != null && new File(fullPath+".java").exists())
|
| + {
|
| + sourceFound = fullPath + ".java";
|
| + }
|
| + if (classFound != null && new File(fullPath+".class").exists())
|
| + {
|
| + classFound = fullPath + ".class";
|
| + }
|
| + }
|
| + // TODO(Eric Ayers): IntelliJ static analysis shows that this expression
|
| + // is always true.
|
| + if (classFound == null)
|
| + {
|
| + deletedClasses.add(key);
|
| + if (e.javaFileFullPath.endsWith(".jar"))
|
| + {
|
| + deletedJarFiles.add(e.javaFileFullPath);
|
| + }
|
| + else
|
| + {
|
| + initializeClassFileFullPath(e);
|
| + (new File(e.classFileFullPath)).delete();
|
| + }
|
| + }
|
| + else if (sourceFound != null)
|
| + {
|
| + newJavaFiles.add(sourceFound);
|
| + e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER;
|
| + e.oldClassFileFingerprint = projectJavaAndJarFilesArray.length;
|
| + }
|
| + else
|
| + {
|
| + classFound = classFound.replace('/', File.separatorChar);
|
| + throw new PrivateException(new FileNotFoundException("deleted class " + classFound + " still exists."));
|
| + }
|
| + }
|
| + }
|
| + if (e.javaFileFullPath.endsWith(".jar")) {
|
| + deletedJarFiles.add(e.javaFileFullPath);
|
| + } else { // Try to delete a class file for the removed project class.
|
| + initializeClassFileFullPath(e);
|
| + (new File(e.classFileFullPath)).delete();
|
| + }
|
| + }
|
| + }
|
| + } else { // projectJavaAndJarFilesArray == null - use supplied arrays of added and removed .java and .jar files
|
| + if (addedJavaAndJarFilesArray != null) {
|
| + for (String fileName : addedJavaAndJarFilesArray) {
|
| + fileName = fileName.intern();
|
| + if (fileName.endsWith(".java")) {
|
| + newJavaFiles.add(fileName);
|
| + } else if (fileName.endsWith(".jar")) {
|
| + newJarFiles.add(fileName);
|
| + } else {
|
| + throw new PrivateException(new PublicExceptions.InvalidSourceFileExtensionException(
|
| + "specified source file " + fileName + " has an invalid extension (not .java or .jar)."));
|
| + }
|
| + }
|
| + }
|
| +
|
| + Set<String> removedJavaAndJarFilesSet = null;
|
| + if (removedJavaAndJarFilesArray != null) {
|
| + removedJavaAndJarFilesSet = new LinkedHashSet<String>();
|
| + for (String fileName : removedJavaAndJarFilesArray) {
|
| + fileName = fileName.intern();
|
| + removedJavaAndJarFilesSet.add(fileName);
|
| + if (fileName.endsWith(".jar")) {
|
| + deletedJarFiles.add(fileName);
|
| + }
|
| + }
|
| + }
|
| +
|
| + for (Entry<String, PCDEntry> entry : pcd.entrySet()) {
|
| + String key = entry.getKey();
|
| + PCDEntry e = entry.getValue();
|
| + e.oldClassInfo.restorePCDM(this);
|
| + if (removedJavaAndJarFilesSet != null &&
|
| + removedJavaAndJarFilesSet.contains(e.javaFileFullPath)) {
|
| + deletedClasses.add(key);
|
| + if (!e.javaFileFullPath.endsWith(".jar")) { // Try to delete a class file for the removed project class.
|
| + initializeClassFileFullPath(e);
|
| + (new File(e.classFileFullPath)).delete();
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * In the end of run, update the information in the project database for the class files which have
|
| + * been updated and checked, or deleted. If compilationResult == 0, i.e. all recompilations were
|
| + * successful, information for new versions of all of the classes is made permanent, and entries
|
| + * for deleted classes are removed permanently. Otherwise, information is updated only for those
|
| + * classes whose old and new versions were found source compatible.
|
| + */
|
| + private void updateClassFilesInfoInPCD(int compilationResult) {
|
| + // If the project appears to be inconsistent after changes, make a preliminary pass that will deal with enclosing
|
| + // classes for deleted nested classes. The problem with them can be as follows: we delete a nested class C$X,
|
| + // which is still referenced from somewhere. However, C has not changed at all or at least incompatibly, and
|
| + // thus we update its PCDEntry, which now does not reference C$X. Other parts of jmake require that a nested
|
| + // class is always referenced from its directly enclusing class, thus to keep the PCD consistent we have to remove
|
| + // 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
|
| + // may still reference it and have not been updated are not checked => project becomes inconsistent. We could do
|
| + // better by immediately marking enclosing classes incompatible once we detect that a deleted nested class is
|
| + // really referenced from somewhere, but the solution below seems to be more robust.
|
| + if (compilationResult != 0) {
|
| + for (String className : updatedAndCheckedClasses) {
|
| + PCDEntry entry = pcd.get(className);
|
| + if (entry.checkResult == PCDEntry.CV_DELETED &&
|
| + !"".equals(entry.oldClassInfo.directlyEnclosingClass)) {
|
| + PCDEntry enclEntry =
|
| + pcd.get(entry.oldClassInfo.directlyEnclosingClass);
|
| + enclEntry.checkResult = PCDEntry.CV_INCOMPATIBLE;
|
| + }
|
| + }
|
| + }
|
| +
|
| + for (String className : updatedAndCheckedClasses) {
|
| + PCDEntry entry = pcd.get(className);
|
| + if (entry.checkResult == PCDEntry.CV_UNCHECKED) {
|
| + continue;
|
| + }
|
| + if (ClassPath.getVirtualPath() != null) {
|
| + if (entry.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) {
|
| + continue;
|
| + }
|
| + }
|
| + if (entry.checkResult == PCDEntry.CV_DELETED) {
|
| + if (compilationResult == 0) {
|
| + pcd.remove(className); // Only if consistency checking is ok, a deleted class can be safely removed from the PCD
|
| + }
|
| + } else if (entry.checkResult == PCDEntry.CV_COMPATIBLE ||
|
| + entry.checkResult == PCDEntry.CV_NEW ||
|
| + (entry.checkResult == PCDEntry.CV_INCOMPATIBLE && compilationResult == 0)) {
|
| + if (entry.newClassInfo == null) { // "Safety net" for the (hopefully unlikely) case we overlooked something before...
|
| + Utils.printWarningMessage("Warning: internal information inconsistency detected during pdb updating");
|
| + Utils.printWarningMessage(Utils.REPORT_PROBLEM);
|
| + Utils.printWarningMessage("Class name: " + className);
|
| + if (entry.checkResult == PCDEntry.CV_NEW) {
|
| + pcd.remove(className);
|
| + } else {
|
| + continue;
|
| + }
|
| + }
|
| + entry.oldClassFileLastModified = entry.newClassFileLastModified;
|
| + entry.oldClassFileFingerprint = entry.newClassFileFingerprint;
|
| + entry.oldClassInfo = entry.newClassInfo;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Find all .java files on the filesystem, for which the .class file does not exist
|
| + * or is newer than the .java file. Also find all .jar files for which the timestamp
|
| + * has changed. Alternatively, just use the supplied array of updated .java/.jar files.
|
| + */
|
| + private void findUpdatedJavaAndJarFiles() {
|
| + boolean projectSpecifiedAsAllSources =
|
| + projectJavaAndJarFilesArray != null;
|
| + for (PCDEntry entry : entries()) {
|
| + if (deletedClasses.contains(entry.className)) {
|
| + continue;
|
| + }
|
| + if (entry.javaFileFullPath.endsWith(".java")) {
|
| + initializeClassFileFullPath(entry);
|
| + if (projectSpecifiedAsAllSources) {
|
| + if (ClassPath.getVirtualPath() != null) {
|
| + String paths[] = ClassPath.getVirtualPath().split(File.pathSeparator);
|
| + String tmpClassName = entry.className;
|
| + tmpClassName = tmpClassName.replaceAll("\\Q$\\E.*$", "");
|
| + for (int i=0; i<paths.length; i++) {
|
| + String tmpFilename = paths[i] + File.separator + tmpClassName + ".java";
|
| + File tmpFile = new File(tmpFilename);
|
| + if (tmpFile.exists()) {
|
| + entry.javaFileFullPath = tmpFile.getAbsolutePath();
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + Utils.startTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP);
|
| + if (classFileObsoleteOrDeleted(entry)) {
|
| + updatedJavaFiles.add(entry.javaFileFullPath);
|
| + }
|
| + Utils.stopAndAddTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP, Utils.TIMING_CLASS_FILE_OBSOLETE_OR_DELETED);
|
| + }
|
| + entry.checked = true;
|
| + } else { // Class coming from a .jar file. Mark this entry as checked only if its JAR hasn't changed
|
| + if (projectJavaAndJarFilesArray != null) {
|
| + entry.checked = !checkJarFileForUpdate(entry);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Lists of updated/added/deleted source files specified instead of a full list of project sources
|
| + if (!projectSpecifiedAsAllSources && updatedJavaAndJarFilesArray != null) {
|
| + for (int i = 0; i < updatedJavaAndJarFilesArray.length; i++) {
|
| + if (updatedJavaAndJarFilesArray[i].endsWith(".java")) {
|
| + updatedJavaFiles.add(updatedJavaAndJarFilesArray[i]);
|
| + } else {
|
| + updatedJarFiles.add(updatedJavaAndJarFilesArray[i]);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + private boolean classFileObsoleteOrDeleted(PCDEntry entry) {
|
| + if (ClassPath.getVirtualPath() != null) {
|
| + File file1 = new File(entry.javaFileFullPath);
|
| + if (!file1.exists())
|
| + throw new PrivateException(new FileNotFoundException("specified source file " +
|
| + entry.javaFileFullPath + " not found."));
|
| + if (file1.lastModified() < entry.oldClassFileLastModified)
|
| + {
|
| + return false;
|
| + }
|
| + }
|
| + File classFile = Utils.checkFileForName(entry.classFileFullPath);
|
| + if (classFile == null || !classFile.exists()) {
|
| + return true; // Class file has been deleted
|
| + }
|
| + File javaFile = new File(entry.javaFileFullPath); // Guaranteed to exist at this point
|
| + if (classFile.lastModified() <= javaFile.lastModified()) {
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + private boolean checkJarFileForUpdate(PCDEntry entry) {
|
| + String jarFileName = entry.javaFileFullPath;
|
| + if (stableJarFiles.contains(jarFileName)) {
|
| + return false;
|
| + } else if (updatedJarFiles.contains(jarFileName) ||
|
| + newJarFiles.contains(jarFileName) ||
|
| + deletedJarFiles.contains(jarFileName)) {
|
| + return true;
|
| + } else {
|
| + File jarFile = new File(jarFileName); // Guaranteed to exist at this point.
|
| + if (entry.oldClassFileLastModified != jarFile.lastModified()) {
|
| + updatedJarFiles.add(jarFileName);
|
| + return true;
|
| + } else {
|
| + stableJarFiles.add(jarFileName);
|
| + return false;
|
| + }
|
| + }
|
| + }
|
| +
|
| + public int recompileUpdatedJavaFiles() {
|
| + if (externalApp != null) {
|
| + return recompileUpdatedJavaFilesUsingExternalMethod();
|
| + } else {
|
| + return recompileUpdatedJavaFilesOurselves();
|
| + }
|
| + }
|
| +
|
| + private int recompileUpdatedJavaFilesOurselves() {
|
| + int filesNo = updatedJavaFiles.size() + newJavaFiles.size();
|
| + int addArgsNo = javacAddArgs.size();
|
| + int argsNo = addArgsNo + filesNo + 2;
|
| + String compilerBootClassPath, compilerExtDirs;
|
| + if ((compilerBootClassPath = ClassPath.getCompilerBootClassPath()) != null) {
|
| + argsNo += 2;
|
| + }
|
| + if ((compilerExtDirs = ClassPath.getCompilerExtDirs()) != null) {
|
| + argsNo += 2;
|
| + }
|
| + if (jcExecApp != null) {
|
| + argsNo++;
|
| + }
|
| + String args[] = new String[argsNo];
|
| + int pos = 0;
|
| + if (jcExecApp != null) {
|
| + args[pos++] = jcExecApp;
|
| + }
|
| + for (int i = 0; i < addArgsNo; i++) {
|
| + args[pos++] = javacAddArgs.get(i);
|
| + }
|
| + args[pos++] = "-classpath";
|
| + args[pos++] = ClassPath.getCompilerUserClassPath();
|
| + if (compilerBootClassPath != null) {
|
| + args[pos++] = "-bootclasspath";
|
| + args[pos++] = compilerBootClassPath;
|
| + }
|
| + if (compilerExtDirs != null) {
|
| + args[pos++] = "-extdirs";
|
| + args[pos++] = compilerExtDirs;
|
| + }
|
| + if (!newProject) {
|
| + Utils.printInfoMessage("Recompiling source files:");
|
| + }
|
| + for (String javaFileFullPath : updatedJavaFiles) {
|
| + if (!newProject) {
|
| + Utils.printInfoMessage(javaFileFullPath);
|
| + }
|
| + recompiledJavaFiles.add(args[pos++] = javaFileFullPath);
|
| + }
|
| + for (int j = 0; j < newJavaFiles.size(); j++) {
|
| + String javaFileFullPath = newJavaFiles.get(j);
|
| + if (!newProject) {
|
| + Utils.printInfoMessage(javaFileFullPath);
|
| + }
|
| + recompiledJavaFiles.add(args[pos++] = javaFileFullPath);
|
| + }
|
| +
|
| + if (jcExecApp == null) { // Executing javac or some other compiler within the same JVM
|
| + Object reflectArgs[] = new Object[1];
|
| + reflectArgs[0] = args;
|
| + try {
|
| + Object dummy = compilerClass.newInstance();
|
| + Integer res = (Integer) compileMethod.invoke(dummy, reflectArgs);
|
| + return res.intValue();
|
| + } catch (Exception e) {
|
| + throw compilerInteractionException("exception thrown when trying to invoke the compiler method", e, 0);
|
| + }
|
| + } else { // Executing an external Java compiler, such as jikes
|
| + int exitCode = 0;
|
| + try {
|
| + Process p = Runtime.getRuntime().exec(args);
|
| + InputStream pErr = p.getErrorStream();
|
| + InputStream pOut = p.getInputStream();
|
| + boolean terminated = false;
|
| +
|
| + while (!terminated) {
|
| + try {
|
| + exitCode = p.exitValue();
|
| + terminated = true;
|
| + } catch (IllegalThreadStateException itse) { // Process not yet terminated, wait for some time
|
| + Utils.ignore(itse);
|
| + Utils.delay(100);
|
| + }
|
| + try {
|
| + Utils.readAndPrintBytesFromStream(pErr, System.err);
|
| + Utils.readAndPrintBytesFromStream(pOut, System.out);
|
| + } catch (IOException ioe1) {
|
| + throw compilerInteractionException("I/O error when reading the compiler application output", ioe1, exitCode);
|
| + }
|
| + }
|
| + return exitCode;
|
| + } catch (IOException ioe2) {
|
| + throw compilerInteractionException("I/O error when trying to invoke the compiler application", ioe2, exitCode);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /** Execution under complete control of external app - use externally supplied method to recompile classes */
|
| + private int recompileUpdatedJavaFilesUsingExternalMethod() {
|
| + int filesNo = updatedJavaFiles.size() + newJavaFiles.size();
|
| + String[] fileNames = new String[filesNo];
|
| + int i = 0;
|
| + for (String updatedFile : updatedJavaFiles) {
|
| + recompiledJavaFiles.add(fileNames[i] = updatedFile);
|
| + }
|
| + for (int j = 0; j < newJavaFiles.size(); j++) {
|
| + recompiledJavaFiles.add(fileNames[i++] = newJavaFiles.get(j));
|
| + }
|
| +
|
| + try {
|
| + Integer res =
|
| + (Integer) externalCompileSourceFilesMethod.invoke(externalApp, new Object[]{fileNames});
|
| + return res.intValue();
|
| + } catch (IllegalAccessException e1) {
|
| + throw compilerInteractionException("compiler method is not accessible", e1, 0);
|
| + } catch (IllegalArgumentException e2) {
|
| + throw compilerInteractionException("illegal arguments passed to compiler method", e2, 0);
|
| + } catch (InvocationTargetException e3) {
|
| + throw compilerInteractionException("exception when executing the compiler method", e3, 0);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * For each .java file from newJavaFiles, find all of the .class files, the names of which we can
|
| + * logically deduce (a top-level class with the same name, and all of the nested classes),
|
| + * and put the info on them into the PCD. Also include any class files from the dependencyFile,
|
| + * if any. For each .jar file from newJarFiles, find all of the .class files in that archive and
|
| + * put info on them into the PCD.
|
| + */
|
| + private void findClassFilesForNewJavaAndJarFiles() {
|
| + for (String javaFileFullPath : newJavaFiles) {
|
| + PCDEntry pcde =
|
| + findClassFileOnFilesystem(javaFileFullPath, null, null, false);
|
| +
|
| + if (pcde == null) {
|
| + // .class file not found - possible compilation error
|
| + if (missingClassIsOk(javaFileFullPath)) {
|
| + continue;
|
| + } else {
|
| + throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
|
| + "Could not find class file for " + javaFileFullPath));
|
| + }
|
| + }
|
| + Set<String> entries = new HashSet<String>();
|
| + if (pcde.checkResult == PCDEntry.CV_NEW) { // It's really a new .java file, not a moved one
|
| + entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, false));
|
| + } else {
|
| + entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, true));
|
| + }
|
| + entries.add(pcde.className);
|
| + if (dependencyFile != null) {
|
| + Map<String, List<String>> dependencies = parseDependencyFile();
|
| + List<String> myDeps = dependencies.get(javaFileFullPath);
|
| + if (myDeps != null) {
|
| + for (String dependency : myDeps) {
|
| + if (entries.contains(dependency))
|
| + continue;
|
| + findClassFileOnFilesystem(javaFileFullPath, pcde,
|
| + dependency, false);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + for (String newJarFile : newJarFiles) {
|
| + processAllClassesFromJarFile(newJarFile);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Parse an extra dependency file. The format of the file is a series of lines,
|
| + * each consisting of:
|
| + * SourceFileName.java -> ClassName
|
| + * (these file names are relative to destDir)
|
| + */
|
| + private Map<String, List<String>> parseDependencyFile() {
|
| + if (!destDirSpecified)
|
| + throw new RuntimeException("Dependency files require destDir");
|
| + if (extraDependencies != null)
|
| + return extraDependencies;
|
| + BufferedReader in = null;
|
| + try {
|
| + extraDependencies = new HashMap<String, List<String>>();
|
| + in = new BufferedReader(new FileReader(dependencyFile));
|
| + int lineNumber = 0;
|
| + while (true) {
|
| + lineNumber ++;
|
| + String line = in.readLine();
|
| + if (line == null)
|
| + break;
|
| + String[] parts = line.split("->");
|
| + if (parts.length != 2) {
|
| + throw new RuntimeException("Failed to parse line " + lineNumber + " of " + dependencyFile
|
| + + ". Expected {foo.java} -> {classname}.");
|
| + }
|
| + String src = parts[0].trim();
|
| + src = new File(destDir, src).getCanonicalPath();
|
| + String cls = parts[1].trim();
|
| + List<String> classes = extraDependencies.get(src);
|
| + if (classes == null) {
|
| + classes = new ArrayList<String>();
|
| + extraDependencies.put(src, classes);
|
| + }
|
| + cls = cls.substring(0, cls.length() - 6); // strip trailing ".class"
|
| + classes.add(cls);
|
| + }
|
| + } catch (IOException e) {
|
| + throw new PrivateException(e);
|
| + } finally {
|
| + if (in != null)
|
| + try {
|
| + in.close();
|
| + } catch (IOException e) {
|
| + throw new RuntimeException(e);
|
| + }
|
| + }
|
| + return extraDependencies;
|
| + }
|
| +
|
| + /**
|
| + * In most cases we want to fail the build if a class cannot be found.
|
| + *
|
| + * However there is one common valid case where a .java file might not contain
|
| + * a class: package-info.java files.
|
| + *
|
| + * See this doc for more info: http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html
|
| + */
|
| + private boolean missingClassIsOk(String javaFileFullPath) {
|
| + return javaFileFullPath != null && "package-info.java".equals(new File(javaFileFullPath).getName());
|
| + }
|
| +
|
| + /**
|
| + * Find the .class file for the given javaFileFullPath and create a new PCDEntry for it.
|
| + * If enclosingClassPCDE is null, the named top-level class for the given .java file is looked up.
|
| + * Otherwise, the specified class specified by nestedClassFullName is looked up.
|
| + */
|
| + private PCDEntry findClassFileOnFilesystem(String javaFileFullPath, PCDEntry enclosingClassPCDE, String nestedClassFullName, boolean isNested) {
|
| + String classFileFullPath = null;
|
| + String fullClassName;
|
| + File classFile = null;
|
| +
|
| + if (enclosingClassPCDE == null) { // Looking for a top-level class. May need to locate an appropriate directory.
|
| + // Remove the ".java" suffix. A Windows disk-name prefix, such as 'c:', will be cut off later automatically
|
| + fullClassName =
|
| + javaFileFullPath.substring(0, javaFileFullPath.length() - 5);
|
| + if (destDirSpecified) {
|
| + // Search for the .class file. We first assume the longest possible name. In case of failure,
|
| + // we cut the assumed top-most package from it and repeat the search.
|
| + while (classFile == null) {
|
| + classFileFullPath = destDir + fullClassName + ".class";
|
| + classFile = Utils.checkFileForName(classFileFullPath);
|
| + if (classFile == null) {
|
| + int cutIndex = fullClassName.indexOf(File.separatorChar);
|
| + if (cutIndex == -1) {
|
| + // Most probably, there was an error during compilation of this file.
|
| + // This does not prevent us from continuing.
|
| + Utils.printWarningMessage("Warning: unable to find .class file corresponding to source " + javaFileFullPath + ": expected " + classFileFullPath);
|
| +
|
| + return null;
|
| + }
|
| + fullClassName = fullClassName.substring(cutIndex + 1);
|
| + }
|
| + }
|
| + } else {
|
| + classFileFullPath = fullClassName + ".class";
|
| + classFile = Utils.checkFileForName(classFileFullPath);
|
| + if (classFile == null) {
|
| + Utils.printWarningMessage("Warning: unable to find .class file corresponding to source " + javaFileFullPath);
|
| + return null;
|
| + }
|
| + }
|
| + } else { // Looking for a nested class, which always sits in the same directory as its enclosing class
|
| + classFileFullPath =
|
| + Utils.getClassFileFullPathForNestedClass(enclosingClassPCDE.classFileFullPath, nestedClassFullName);
|
| + classFile = Utils.checkFileForName(classFileFullPath);
|
| + if (classFile == null) {
|
| + Utils.printWarningMessage("Warning: unable to find .class file corresponding to nested class " + nestedClassFullName);
|
| + return null;
|
| + }
|
| + fullClassName = nestedClassFullName;
|
| + }
|
| +
|
| + if (backSlashFileSeparator) {
|
| + fullClassName = fullClassName.replace(File.separatorChar, '/');
|
| + }
|
| +
|
| + byte classFileBytes[] = Utils.readFileIntoBuffer(classFile);
|
| + ClassInfo classInfo =
|
| + new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, classFileFullPath);
|
| + if (isNested) {
|
| + if (!classInfo.directlyEnclosingClass.equals(enclosingClassPCDE.newClassInfo.name)) {
|
| + // Check if the above strings are like A and A$1. If so, there is actually no problem - the correct
|
| + // answer is A$1. The reason why just A was determined as a directly enclosing class when parsing
|
| + // class classInfo is due to the ambiguous interpretation of names like A$1$B. Such a name may mean
|
| + // (1) a non-member local nested class B of A, or (2) a member class B of an anonymous nested class A$1.
|
| + // When parsing any non-toplevel class, the first interpretation is always used.
|
| + // NOTE FOR JDK 1.5 - starting from this version, there is no ambiguity anymore.
|
| + // (1) will be called A$1B, and (2) will still be A$1$B
|
| + String a = classInfo.directlyEnclosingClass;
|
| + String ad1 = enclosingClassPCDE.newClassInfo.name;
|
| + if (!((classInfo.javacTargetRelease == Utils.JAVAC_TARGET_RELEASE_OLDEST) &&
|
| + (ad1.startsWith(a + "$") && Character.isDigit(ad1.charAt(a.length() + 1))))) {
|
| + throw new PrivateException(new PublicExceptions.ClassFileParseException(
|
| + "Enclosing class names for class " + classInfo.name + " don't match:\n" +
|
| + classInfo.directlyEnclosingClass + " and " + enclosingClassPCDE.newClassInfo.name));
|
| + }
|
| + }
|
| + }
|
| +
|
| + // If dest dir was specified, check if the deduced name is equal to the one in this class (in this case
|
| + // they should necessarily match). Otherwise, without parsing the .java file, we can't reliably say what the
|
| + // full class name (actually, its package part) should be - so we just note the name.
|
| + if (destDirSpecified) {
|
| + if (!fullClassName.equals(classInfo.name)) {
|
| + throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
|
| + "Error: deduced class name is different from the real one for source " +
|
| + javaFileFullPath + "\n" + fullClassName + " and " + classInfo.name));
|
| + }
|
| + } else {
|
| + fullClassName = classInfo.name;
|
| + }
|
| +
|
| + if (enclosingClassPCDE != null) {
|
| + javaFileFullPath = enclosingClassPCDE.javaFileFullPath;
|
| + }
|
| + long classFileLastMod = classFile.lastModified();
|
| + long classFileFP = computeFP(classFileBytes);
|
| +
|
| + if (pcd.containsKey(fullClassName)) {
|
| + PCDEntry pcde = pcd.get(fullClassName);
|
| + // If this entry has already been checked, it's a second entry for the same class, which is illegal.
|
| + if (pcde.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) {
|
| + // Newer copy of same file found in closer layer
|
| + // Reset to CV_UNCHECKED and skip redundnacy check
|
| + // as we know this would be redundant
|
| + pcde.checkResult = PCDEntry.CV_UNCHECKED;
|
| + } else {
|
| + if (pcde.checked) {
|
| + throw new PrivateException(new PublicExceptions.DoubleEntryException(
|
| + "Two entries for class " + classInfo.name + " detected: " + pcde.javaFileFullPath + " and " + javaFileFullPath));
|
| + }
|
| + }
|
| + // Otherwise, it means that the .java file for this class has been moved. jmake initially interprets
|
| + // a new source file name as a new class, and it's only at this point that we can actually see that it was
|
| + // only a move. We update javaFileFullPath for nested classes after we return from here.
|
| + pcde.javaFileFullPath = javaFileFullPath;
|
| + pcde.classFileFullPath = classFileFullPath;
|
| + pcde.newClassInfo = classInfo;
|
| + if (deletedClasses.contains(fullClassName)) {
|
| + deletedClasses.remove(fullClassName);
|
| + }
|
| + return pcde;
|
| + }
|
| +
|
| + PCDEntry pcde = new PCDEntry(fullClassName,
|
| + javaFileFullPath,
|
| + classFileFullPath, classFileLastMod, classFileFP,
|
| + classInfo);
|
| + pcde.checkResult = PCDEntry.CV_NEW; // So that later it's promoted into oldClassInfo correctly
|
| + updatedAndCheckedClasses.add(fullClassName); // So that the above happens
|
| + pcd.put(fullClassName, pcde);
|
| + return pcde;
|
| + }
|
| +
|
| + /**
|
| + * For the given class, find all direct nested classes (which may include reading their .class files from the
|
| + * class path) and set their access flags (contained in this, enclosing class, object) appropriately. If
|
| + * this class is a one coming from a .java source, repeat the procedure for each nested class in turn.
|
| + * Otherwise, i.e. if a class comes from a .jar, don't bother, since we will come across each of these
|
| + * classes anyway - when scanning their .jar. If 'move' parameter is true, it means that this method is called for
|
| + * a class that is not new, but has been moved (and possibly updated).
|
| + */
|
| + private Set<String> findAndUpdateAllNestedClassesForClass(PCDEntry pcde, boolean move) {
|
| + ClassInfo classInfo = pcde.newClassInfo;
|
| + if (classInfo.nestedClasses == null) {
|
| + return Collections.emptySet();
|
| + }
|
| + Set<String> entries = new LinkedHashSet<String>();
|
| + String nestedClasses[] = classInfo.nestedClasses;
|
| + String javaFileFullPath = pcde.javaFileFullPath;
|
| + String enclosingClassFileFullPath = pcde.classFileFullPath;
|
| + boolean isJavaSourceFile = javaFileFullPath.endsWith(".java");
|
| +
|
| + for (int i = 0; i < nestedClasses.length; i++) {
|
| + PCDEntry nestedPCDE = pcd.get(nestedClasses[i]);
|
| + if (nestedPCDE == null) {
|
| + if (isJavaSourceFile) {
|
| + nestedPCDE =
|
| + findClassFileOnFilesystem(null, pcde, nestedClasses[i], true);
|
| + }
|
| + // For classes that come from a .jar, pcde should already be there. Otherwise this class just doesn't exist.
|
| + if (nestedPCDE == null) {
|
| + // Probably a compilation error, such that enclosing class is compiled but nested is not.
|
| + throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
|
| + "Could not find class file for " + pcde.toString()));
|
| + }
|
| + }
|
| + if (move) {
|
| + if (deletedClasses.contains(nestedClasses[i])) {
|
| + deletedClasses.remove(nestedClasses[i]);
|
| + }
|
| + nestedPCDE.javaFileFullPath = javaFileFullPath;
|
| + if (javaFileFullPath.endsWith(".java")) {
|
| + nestedPCDE.classFileFullPath =
|
| + Utils.getClassFileFullPathForNestedClass(enclosingClassFileFullPath, nestedClasses[i]);
|
| + } else {
|
| + nestedPCDE.classFileFullPath = javaFileFullPath;
|
| + }
|
| + }
|
| + if (nestedPCDE.newClassInfo == null) {
|
| + getClassInfoForPCDEntry(ClassInfo.VER_NEW, nestedPCDE);
|
| + }
|
| + nestedPCDE.newClassInfo.accessFlags =
|
| + pcde.newClassInfo.nestedClassAccessFlags[i];
|
| + nestedPCDE.newClassInfo.isNonMemberNestedClass =
|
| + pcde.newClassInfo.nestedClassNonMember[i];
|
| +
|
| + entries.add(nestedPCDE.className);
|
| + entries.addAll(findAndUpdateAllNestedClassesForClass(nestedPCDE, move));
|
| + }
|
| + return entries;
|
| + }
|
| +
|
| + /**
|
| + * Take care of new nested classes that could have been generated from already existing .java sources,
|
| + * and of nested classes that do not exist anymore because they were deleted from these sources.
|
| + */
|
| + private void dealWithNestedClassesForUpdatedJavaFiles() {
|
| + if (updatedJavaFiles.size() == 0) {
|
| + return;
|
| + }
|
| +
|
| + // First put PCDEntries for all updated classes that have nested classes into a temporary list.
|
| + // That's because we can then find new nested classes, which we will need to add to the PCD, which
|
| + // may probably conflict with us still iterating over it.
|
| + List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>();
|
| + for (PCDEntry pcde : entries()) {
|
| + if (pcde.checkResult == PCDEntry.CV_NEW) {
|
| + continue; // This class has just been added to the PCD
|
| + }
|
| + if (updatedJavaFiles.contains(pcde.javaFileFullPath)) {
|
| + ClassInfo oldClassInfo = pcde.oldClassInfo;
|
| + ClassInfo newClassInfo =
|
| + getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde);
|
| + if (newClassInfo == null) {
|
| + deletedClasses.add(pcde.className);
|
| + continue; // Class file deleted then not re-created due to a compilation error somewhere.
|
| + }
|
| + if (oldClassInfo.nestedClasses != null || newClassInfo.nestedClasses != null) {
|
| + updatedEntries.add(pcde);
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (dependencyFile != null) {
|
| + Map<String, List<String>> dependencies = parseDependencyFile();
|
| + for (String file : updatedJavaFiles) {
|
| + List<String> myDeps = dependencies.get(file);
|
| + if (myDeps == null)
|
| + continue;
|
| + PCDEntry pcde = getNamedPCDE(file, dependencies);
|
| + for (String dependency : myDeps) {
|
| + PCDEntry dep = pcd.get(dependency);
|
| + if (dep != null)
|
| + // This is an existing dep.
|
| + continue;
|
| + dep = findClassFileOnFilesystem(file, pcde, dependency, false);
|
| + getClassInfoForPCDEntry(ClassInfo.VER_NEW, dep);
|
| + if (dep.newClassInfo.nestedClasses != null)
|
| + updatedEntries.add(dep);
|
| + }
|
| + }
|
| + }
|
| + dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false);
|
| + }
|
| +
|
| + private PCDEntry getNamedPCDE(String file, Map<String, List<String>> dependencies) {
|
| + List<String> depsForFile = dependencies.get(file);
|
| + PCDEntry pcde = null;
|
| + // Find a non-nested class for this java file for which we already have
|
| + // a pcde
|
| + for (String dependency : depsForFile) {
|
| + if (dependency.indexOf('$') != -1)
|
| + continue;
|
| + pcde = pcd.get(dependency);
|
| + if (pcde != null)
|
| + break;
|
| + }
|
| + if (pcde == null) {
|
| + throw new PrivateException(new PublicExceptions.InternalException(file
|
| + + " was supposed to be an updated file, but there are no PCDEntries for any of its deps"));
|
| + }
|
| + return pcde;
|
| + }
|
| +
|
| + private void dealWithNestedClassesForUpdatedPCDEntries(List<PCDEntry> entries, boolean move) {
|
| + for (int i = 0; i < entries.size(); i++) {
|
| + PCDEntry pcde = entries.get(i);
|
| + ClassInfo oldClassInfo = pcde.oldClassInfo;
|
| + ClassInfo newClassInfo = pcde.newClassInfo;
|
| + if (newClassInfo.nestedClasses != null) {
|
| + Set<String> nested = findAndUpdateAllNestedClassesForClass(pcde, move);
|
| + if (oldClassInfo.nestedClasses != null) { // Check if any old nested classes don't exist anymore
|
| + for (int j = 0; j < oldClassInfo.nestedClasses.length; j++) {
|
| + boolean found = false;
|
| + String oldNestedClass = oldClassInfo.nestedClasses[j];
|
| + for (int k = 0; k < newClassInfo.nestedClasses.length; k++) {
|
| + if (oldNestedClass.equals(newClassInfo.nestedClasses[k])) {
|
| + found = true;
|
| + break;
|
| + }
|
| + }
|
| + if (!found) {
|
| + deletedClasses.add(oldNestedClass);
|
| + }
|
| + }
|
| + }
|
| + } else { // newNestedClasses == null and oldNestedClasses != null, so all nested classes have been removed in the new version
|
| + for (int j = 0; j < oldClassInfo.nestedClasses.length; j++) {
|
| + deletedClasses.add(oldClassInfo.nestedClasses[j]);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + private void findUpdatedClasses() {
|
| + // This (iterating over all of the classes once again after performing that in classFileObsoleteOrDeleted()) may
|
| + // seem time-consuming, but in reality it isn't, since the most time-consuming operation of obtaining internal
|
| + // file handles for class files has already been performed in classFileObsoleteOrDeleted(). Once we have done that,
|
| + // this re-iteration takes very small amount of time. However, if we switch from "class file older than .java
|
| + // file" to ".java file timestamp changed" condition for recompilation, this will have to be changed as well.
|
| + for (PCDEntry entry : entries()) {
|
| + String className = entry.className;
|
| + if (updatedAndCheckedClasses.contains(className) ||
|
| + deletedClasses.contains(className)) {
|
| + continue;
|
| + }
|
| + if (!entry.javaFileFullPath.endsWith(".java")) {
|
| + continue; // classes from (updated) .jars have been dealt with separately
|
| + }
|
| + //DAB TODO understand this bit better. It is needed to support -vpath, I'm just not sure why....
|
| + if (entry.checkResult != PCDEntry.CV_NEWER_FOUND_NEARER &&
|
| + !updatedAndCheckedClasses.contains(className) &&
|
| + !deletedClasses.contains(className) &&
|
| + entry.javaFileFullPath.endsWith(".java") &&
|
| + classFileUpdated(entry))
|
| + {
|
| + //DAB TODO this is the old way....
|
| + //DAB if (classFileUpdated(entry)) {
|
| + updatedClasses.add(className);
|
| + allUpdatedClasses.add(className);
|
| + }
|
| + }
|
| + }
|
| +
|
| + private boolean classFileUpdated(PCDEntry entry) {
|
| + File classFile = Utils.checkFileForName(entry.classFileFullPath);
|
| + if (classFile == null) {
|
| + return false;
|
| + }
|
| + // The only case when the above can happen is if class file was first deleted, and then there
|
| + // was an error recompiling its source
|
| +
|
| + long classFileLastMod = classFile.lastModified();
|
| +
|
| + if (classFileLastMod > entry.oldClassFileLastModified) {
|
| + entry.newClassFileLastModified = classFileLastMod;
|
| + // Check if the class was actually modified, to avoid the costly procedure of detailed version compare
|
| + long classFileFP = computeFP(classFile);
|
| + if (classFileFP != entry.oldClassFileFingerprint) {
|
| + entry.newClassFileFingerprint = classFileFP;
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Compare old (preserved in pdb) and new (file system) versions of updated classes, and find all
|
| + * potentially affected dependent classes.
|
| + */
|
| + private void checkUpdatedClasses() {
|
| + for (String className : updatedClasses) {
|
| + PCDEntry pcde = pcd.get(className);
|
| + getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde);
|
| + if (!"".equals(pcde.oldClassInfo.directlyEnclosingClass)) {
|
| + // The following problem can occur with nested classes. A C.java source has been changed, so that C.class is
|
| + // not changed or changed in a compatible way, whereas the access modifiers of C$X.class are changed in an
|
| + // incompatible way, so that something is broken in the project. When jmake is called for the first time,
|
| + // it reports the problem, then saves the info on the new version of C in the pdb. Of course, the record for
|
| + // C$X in the pdb is not updated, since the change to it is incompatible and recompilation of dependent sources
|
| + // has failed. Suppose we don't change anything and invoke jmake again. C$X is found different from its old
|
| + // version and is checked here again. The outcome should be the same. But since C has not changed, C.class is
|
| + // not read from disk and the access flags of C$X, which are stored in C.class, are not set appropriately. So
|
| + // in such circumstances we have wrong access flags for C$X here. To fix the problem we need to load C explicitly.
|
| + ClassInfo enclosingClassInfo =
|
| + getClassInfoForName(ClassInfo.VER_NEW, pcde.oldClassInfo.directlyEnclosingClass);
|
| + //if (enclosingClassInfo == null || enclosingClassInfo.nestedClasses == null) {
|
| + // System.out.println("!!! Suspicious updated class name = " + className);
|
| + // System.out.println("!!! enclosingClassInfo for it = " + enclosingClassInfo);
|
| + // if (enclosingClassInfo != null) {
|
| + // System.out.println("!!! enclosingClassInfo.name = " + enclosingClassInfo.name);
|
| + // if (enclosingClassInfo.nestedClasses == null) System.out.println("!!! enclosingClassInfo.nestedClasses = null");
|
| + // }
|
| + //}
|
| + if (enclosingClassInfo.nestedClasses != null) { // Can be that this nested class was the only one for enclosing class, and it's deleted now
|
| + for (int i = 0; i < enclosingClassInfo.nestedClasses.length; i++) {
|
| + if (className.equals(enclosingClassInfo.nestedClasses[i])) {
|
| + pcde.newClassInfo.accessFlags =
|
| + enclosingClassInfo.nestedClassAccessFlags[i];
|
| + pcde.newClassInfo.isNonMemberNestedClass =
|
| + enclosingClassInfo.nestedClassNonMember[i];
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + if (!(pcde.oldClassInfo.isNonMemberNestedClass && pcde.newClassInfo.isNonMemberNestedClass)) {
|
| + Utils.printInfoMessage("Checking " + pcde.className);
|
| + pcde.checkResult = cv.compareClassVersions(pcde) ? PCDEntry.CV_COMPATIBLE
|
| + : PCDEntry.CV_INCOMPATIBLE;
|
| + String affectedClasses[] = cv.getAffectedClasses();
|
| + if (affectedClasses != null) {
|
| + for (int i = 0; i < affectedClasses.length; i++) {
|
| + PCDEntry affEntry = pcd.get(affectedClasses[i]);
|
| + updatedJavaFiles.add(affEntry.javaFileFullPath);
|
| + }
|
| + }
|
| + } else {
|
| + // A non-member nested class can not be referenced by the source code of any class defined outside the
|
| + // immediately enclosing source code block for this class. Therefore, any incompatibility in the new
|
| + // version of this class can affect only classes that are defined in the same source file - and they
|
| + // are necessarily recompiled together with this class. So there is no point in initiating version
|
| + // compare for this class. However, the new class version should always tembe promoted into the store, since
|
| + // this class itself may depend on other changing classes.
|
| + pcde.checkResult = PCDEntry.CV_COMPATIBLE;
|
| + }
|
| +
|
| + updatedAndCheckedClasses.add(className);
|
| + }
|
| + }
|
| +
|
| + /** Find all dependent classes for deleted classes. */
|
| + private void checkDeletedClasses() {
|
| + for (String className : deletedClasses) {
|
| + PCDEntry pcde = pcd.get(className);
|
| +
|
| + if (pcde == null) { // "Safety net" for the (hopefully unlikely) case. I observed it just once and couldn't identify the reason
|
| + Utils.printWarningMessage("Warning: internal information inconsistency when checking deleted classes");
|
| + Utils.printWarningMessage(Utils.REPORT_PROBLEM);
|
| + Utils.printWarningMessage("Class name: " + className);
|
| + continue;
|
| + }
|
| +
|
| + ClassInfo oldCI = pcde.oldClassInfo;
|
| + if (!oldCI.isNonMemberNestedClass) { // See the comment above
|
| + Utils.printInfoMessage("Checking deleted class " + oldCI.name);
|
| + cv.checkDeletedClass(pcde);
|
| + String[] affectedClasses = cv.getAffectedClasses();
|
| + if (affectedClasses != null) {
|
| + for (int i = 0; i < affectedClasses.length; i++) {
|
| + PCDEntry affEntry = pcd.get(affectedClasses[i]);
|
| + if (deletedClasses.contains(affEntry.className)) {
|
| + continue;
|
| + }
|
| + updatedJavaFiles.add(affEntry.javaFileFullPath);
|
| + }
|
| + }
|
| + }
|
| + pcde.checkResult = PCDEntry.CV_DELETED;
|
| + updatedAndCheckedClasses.add(className);
|
| + }
|
| + deletedClasses.clear();
|
| + }
|
| +
|
| + /**
|
| + * Determine what classes in the given .jar (which may be an existing updated one, or a new one) are new,
|
| + * updated, or moved, and treat them accordingly.
|
| + */
|
| + private void processAllClassesFromJarFile(String jarFileName) {
|
| + JarFile jarFile;
|
| + long jarFileLastMod = 0;
|
| + try {
|
| + File file = new File(jarFileName);
|
| + jarFileLastMod = file.lastModified();
|
| + jarFile = new JarFile(jarFileName);
|
| + } catch (IOException ex) {
|
| + throw new PrivateException(ex);
|
| + }
|
| +
|
| + List<PCDEntry> newEntries = new ArrayList<PCDEntry>();
|
| + List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>();
|
| + List<PCDEntry> movedEntries = new ArrayList<PCDEntry>();
|
| +
|
| + for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
|
| + JarEntry jarEntry = entries.nextElement();
|
| + String fullClassName = jarEntry.getName();
|
| + if (!fullClassName.endsWith(".class")) {
|
| + continue;
|
| + }
|
| + fullClassName =
|
| + fullClassName.substring(0, fullClassName.length() - 6).intern();
|
| + byte classFileBytes[];
|
| + classFileBytes = Utils.readZipEntryIntoBuffer(jarFile, jarEntry);
|
| + long classFileFP = computeFP(classFileBytes);
|
| +
|
| + PCDEntry pcde = pcd.get(fullClassName);
|
| + if (pcde != null) {
|
| + if (pcde.checked) {
|
| + throw new PrivateException(new PublicExceptions.DoubleEntryException(
|
| + "Two entries for class " + fullClassName + " detected: " + pcde.javaFileFullPath + " and " + jarFileName));
|
| + }
|
| + pcde.checked = true;
|
| + pcde.newClassFileLastModified = jarFileLastMod;
|
| + // If we are scanning an existing updated .jar file, and there is no change to the class itself,
|
| + // and it previously was located in the same .jar, do nothing.
|
| + if (pcde.oldClassFileFingerprint == classFileFP &&
|
| + pcde.javaFileFullPath.equals(jarFileName)) {
|
| + pcde.oldClassFileLastModified = jarFileLastMod; // So that next time jmake is inoked, checking
|
| + continue; // of this.jar is not triggered.
|
| + }
|
| + if (pcde.oldClassFileFingerprint != classFileFP) { // This class has been updated
|
| + updatedClasses.add(fullClassName);
|
| + allUpdatedClasses.add(fullClassName);
|
| + pcde.newClassFileLastModified = jarFileLastMod;
|
| + pcde.newClassFileFingerprint = classFileFP;
|
| + pcde.newClassInfo =
|
| + new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
|
| + if (pcde.oldClassInfo.nestedClasses != null || pcde.newClassInfo.nestedClasses != null) {
|
| + updatedEntries.add(pcde);
|
| + }
|
| + } else {
|
| + pcde.oldClassFileLastModified = jarFileLastMod;
|
| + }
|
| + if (!pcde.javaFileFullPath.equals(jarFileName)) {
|
| + // Found an existing class in a different .jar file.
|
| + // May happen if the class file has been moved from one .jar to another (or into a .jar, losing its
|
| + // .java source). It's only at this point that we can actually see that it was really a move.
|
| + if (deletedClasses.contains(fullClassName)) {
|
| + deletedClasses.remove(fullClassName);
|
| + }
|
| + if (pcde.oldClassInfo.nestedClasses != null) {
|
| + movedEntries.add(pcde);
|
| + pcde.newClassInfo =
|
| + new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
|
| + }
|
| + }
|
| + pcde.javaFileFullPath = jarFileName;
|
| + } else { // New class file
|
| + ClassInfo classInfo =
|
| + new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
|
| + pcde = new PCDEntry(fullClassName,
|
| + jarFileName,
|
| + jarFileName, jarFileLastMod, classFileFP,
|
| + classInfo);
|
| + pcde.checkResult = PCDEntry.CV_NEW; // So that later it's promoted into oldClassInfo correctly
|
| + updatedAndCheckedClasses.add(fullClassName); // So that the above happens
|
| + pcd.put(fullClassName, pcde);
|
| + if (pcde.newClassInfo.nestedClasses != null) {
|
| + newEntries.add(pcde);
|
| + }
|
| + }
|
| + }
|
| +
|
| + dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false);
|
| + dealWithNestedClassesForUpdatedPCDEntries(movedEntries, true);
|
| + for (int i = 0; i < newEntries.size(); i++) {
|
| + findAndUpdateAllNestedClassesForClass(newEntries.get(i), false);
|
| + }
|
| + }
|
| +
|
| + /** Determine new, deleted and updated classes coming from updated .jar files. */
|
| + private void dealWithClassesInUpdatedJarFiles() {
|
| + if (updatedJarFiles.size() == 0) {
|
| + return;
|
| + }
|
| +
|
| + for (String updatedJarFile : updatedJarFiles) {
|
| + processAllClassesFromJarFile(updatedJarFile);
|
| + }
|
| +
|
| + // Now scan the PCD to check which classes that come from updated .jar files have not been marked as checked
|
| + for (PCDEntry pcde : entries()) {
|
| + if (updatedJarFiles.contains(pcde.javaFileFullPath)) {
|
| + if (!pcde.checked) {
|
| + deletedClasses.add(pcde.className);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + /** Check if the destination directory exists, and get the canonical path for it. */
|
| + private void initializeDestDir(String inDestDir) {
|
| + if (!(inDestDir == null || inDestDir.equals(""))) {
|
| + File dir = Utils.checkOrCreateDirForName(inDestDir);
|
| + if (dir == null) {
|
| + throw new PrivateException(new IOException("specified directory " + inDestDir + " cannot be created."));
|
| + }
|
| + inDestDir = getCanonicalPath(dir);
|
| + if (!inDestDir.endsWith(File.separator)) {
|
| + inDestDir += File.separatorChar;
|
| + }
|
| + destDir = inDestDir;
|
| + destDirSpecified = true;
|
| + } else {
|
| + destDirSpecified = false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * For the given PCDEntry, set the entry.classFileFullPath according to the value of the .java file full
|
| + * path and the value of the "-d" option at this particular jmake invocation
|
| + */
|
| + private void initializeClassFileFullPath(PCDEntry entry) {
|
| + String classFileFullPath;
|
| + if (destDirSpecified) {
|
| + classFileFullPath = destDir + entry.className + ".class";
|
| + } else {
|
| + String javaFileDir = entry.javaFileFullPath;
|
| + int cutIndex = javaFileDir.lastIndexOf(File.separatorChar);
|
| + if (cutIndex != -1) {
|
| + javaFileDir = javaFileDir.substring(0, cutIndex + 1);
|
| + }
|
| + String classFileName = entry.className;
|
| + cutIndex = classFileName.lastIndexOf('/');
|
| + if (cutIndex != -1) {
|
| + classFileName = classFileName.substring(cutIndex + 1);
|
| + }
|
| + classFileFullPath = javaFileDir + classFileName + ".class";
|
| + }
|
| + if (backSlashFileSeparator) {
|
| + classFileFullPath =
|
| + classFileFullPath.replace('/', File.separatorChar);
|
| + }
|
| + entry.classFileFullPath = classFileFullPath;
|
| + }
|
| +
|
| + private static String getCanonicalPath(File file) {
|
| + try {
|
| + return file.getCanonicalPath().intern();
|
| + } catch (IOException e) {
|
| + throw new PrivateException(e);
|
| + }
|
| + }
|
| +
|
| + private long computeFP(File file) {
|
| + byte buf[] = Utils.readFileIntoBuffer(file);
|
| + return computeFP(buf);
|
| + }
|
| +
|
| + private long computeFP(byte[] buf) {
|
| + checkSum.reset();
|
| + checkSum.update(buf);
|
| + return checkSum.getValue();
|
| + }
|
| +
|
| + private PrivateException compilerInteractionException(String message, Exception origException, int errCode) {
|
| + return new PrivateException(new PublicExceptions.CompilerInteractionException(message, origException, errCode));
|
| + }
|
| +
|
| + private PrivateException internalException(String message) {
|
| + return new PrivateException(new PublicExceptions.InternalException(message));
|
| + }
|
| +}
|
|
|