langtools/src/share/classes/com/sun/tools/sjavac/JavacState.java
changeset 15368 2577ddb7e710
child 19500 26d88d483764
equal deleted inserted replaced
15367:31b57f2b8d0b 15368:2577ddb7e710
       
     1 /*
       
     2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package com.sun.tools.sjavac;
       
    27 
       
    28 import java.io.*;
       
    29 import java.util.Collections;
       
    30 import java.util.Date;
       
    31 import java.util.Set;
       
    32 import java.util.HashSet;
       
    33 import java.util.List;
       
    34 import java.util.Map;
       
    35 import java.util.HashMap;
       
    36 import java.text.SimpleDateFormat;
       
    37 import java.net.URI;
       
    38 import java.util.*;
       
    39 
       
    40 /**
       
    41  * The javac state class maintains the previous (prev) and the current (now)
       
    42  * build states and everything else that goes into the javac_state file.
       
    43  *
       
    44  * <p><b>This is NOT part of any supported API.
       
    45  * If you write code that depends on this, you do so at your own
       
    46  * risk.  This code and its internal interfaces are subject to change
       
    47  * or deletion without notice.</b></p>
       
    48  */
       
    49 public class JavacState
       
    50 {
       
    51     // The arguments to the compile. If not identical, then it cannot
       
    52     // be an incremental build!
       
    53     String theArgs;
       
    54     // The number of cores limits how many threads are used for heavy concurrent work.
       
    55     int numCores;
       
    56 
       
    57     // The bin_dir/javac_state
       
    58     private String javacStateFilename;
       
    59     private File javacState;
       
    60 
       
    61     // The previous build state is loaded from javac_state
       
    62     private BuildState prev;
       
    63     // The current build state is constructed during the build,
       
    64     // then saved as the new javac_state.
       
    65     private BuildState now;
       
    66 
       
    67     // Something has changed in the javac_state. It needs to be saved!
       
    68     private boolean needsSaving;
       
    69     // If this is a new javac_state file, then do not print unnecessary messages.
       
    70     private boolean newJavacState;
       
    71 
       
    72     // These are packages where something has changed and the package
       
    73     // needs to be recompiled. Actions that trigger recompilation:
       
    74     // * source belonging to the package has changed
       
    75     // * artifact belonging to the package is lost, or its timestamp has been changed.
       
    76     // * an unknown artifact has appeared, we simply delete it, but we also trigger a recompilation.
       
    77     // * a package that is tainted, taints all packages that depend on it.
       
    78     private Set<String> taintedPackages;
       
    79     // After a compile, the pubapis are compared with the pubapis stored in the javac state file.
       
    80     // Any packages where the pubapi differ are added to this set.
       
    81     // Later we use this set and the dependency information to taint dependent packages.
       
    82     private Set<String> packagesWithChangedPublicApis;
       
    83     // When a module-info.java file is changed, taint the module,
       
    84     // then taint all modules that depend on that that module.
       
    85     // A module dependency can occur directly through a require, or
       
    86     // indirectly through a module that does a public export for the first tainted module.
       
    87     // When all modules are tainted, then taint all packages belonging to these modules.
       
    88     // Then rebuild. It is perhaps possible (and valuable?) to do a more finegrained examination of the
       
    89     // change in module-info.java, but that will have to wait.
       
    90     private Set<String> taintedModules;
       
    91     // The set of all packages that has been recompiled.
       
    92     // Copy over the javac_state for the packages that did not need recompilation,
       
    93     // verbatim from the previous (prev) to the new (now) build state.
       
    94     private Set<String> recompiledPackages;
       
    95 
       
    96     // The output directories filled with tasty artifacts.
       
    97     private File binDir, gensrcDir, headerDir;
       
    98 
       
    99     // The current status of the file system.
       
   100     private Set<File> binArtifacts;
       
   101     private Set<File> gensrcArtifacts;
       
   102     private Set<File> headerArtifacts;
       
   103 
       
   104     // The status of the sources.
       
   105     Set<Source> removedSources = null;
       
   106     Set<Source> addedSources = null;
       
   107     Set<Source> modifiedSources = null;
       
   108 
       
   109     // Visible sources for linking. These are the only
       
   110     // ones that -sourcepath is allowed to see.
       
   111     Set<URI> visibleSrcs;
       
   112 
       
   113     // Visible classes for linking. These are the only
       
   114     // ones that -classpath is allowed to see.
       
   115     // It maps from a classpath root to the set of visible classes for that root.
       
   116     // If the set is empty, then all classes are visible for that root.
       
   117     // It can also map from a jar file to the set of visible classes for that jar file.
       
   118     Map<URI,Set<String>> visibleClasses;
       
   119 
       
   120     // Setup two transforms that always exist.
       
   121     private CopyFile            copyFiles = new CopyFile();
       
   122     private CompileJavaPackages compileJavaPackages = new CompileJavaPackages();
       
   123 
       
   124     // Where to send stdout and stderr.
       
   125     private PrintStream out, err;
       
   126 
       
   127     JavacState(String[] args, File bd, File gd, File hd, boolean permitUnidentifiedArtifacts, boolean removeJavacState,
       
   128             PrintStream o, PrintStream e) {
       
   129         out = o;
       
   130         err = e;
       
   131         numCores = Main.findNumberOption(args, "-j");
       
   132         theArgs = "";
       
   133         for (String a : removeArgsNotAffectingState(args)) {
       
   134             theArgs = theArgs+a+" ";
       
   135         }
       
   136         binDir = bd;
       
   137         gensrcDir = gd;
       
   138         headerDir = hd;
       
   139         javacStateFilename = binDir.getPath()+File.separator+"javac_state";
       
   140         javacState = new File(javacStateFilename);
       
   141         if (removeJavacState && javacState.exists()) {
       
   142             javacState.delete();
       
   143         }
       
   144         newJavacState = false;
       
   145         if (!javacState.exists()) {
       
   146             newJavacState = true;
       
   147             // If there is no javac_state then delete the contents of all the artifact dirs!
       
   148             // We do not want to risk building a broken incremental build.
       
   149             // BUT since the makefiles still copy things straight into the bin_dir et al,
       
   150             // we avoid deleting files here, if the option --permit-unidentified-classes was supplied.
       
   151             if (!permitUnidentifiedArtifacts) {
       
   152                 deleteContents(binDir);
       
   153                 deleteContents(gensrcDir);
       
   154                 deleteContents(headerDir);
       
   155             }
       
   156             needsSaving = true;
       
   157         }
       
   158         prev = new BuildState();
       
   159         now = new BuildState();
       
   160         taintedPackages = new HashSet<String>();
       
   161         recompiledPackages = new HashSet<String>();
       
   162         packagesWithChangedPublicApis = new HashSet<String>();
       
   163     }
       
   164 
       
   165     public BuildState prev() { return prev; }
       
   166     public BuildState now() { return now; }
       
   167 
       
   168     /**
       
   169      * Remove args not affecting the state.
       
   170      */
       
   171     static String[] removeArgsNotAffectingState(String[] args) {
       
   172         String[] out = new String[args.length];
       
   173         int j = 0;
       
   174         for (int i = 0; i<args.length; ++i) {
       
   175             if (args[i].equals("-j")) {
       
   176                 // Just skip it and skip following value
       
   177                 i++;
       
   178             } else if (args[i].startsWith("--server:")) {
       
   179                 // Just skip it.
       
   180             } else if (args[i].startsWith("--log=")) {
       
   181                 // Just skip it.
       
   182             } else if (args[i].equals("--compare-found-sources")) {
       
   183                 // Just skip it and skip verify file name
       
   184                 i++;
       
   185             } else {
       
   186                 // Copy argument.
       
   187                 out[j] = args[i];
       
   188                 j++;
       
   189             }
       
   190         }
       
   191         String[] ret = new String[j];
       
   192         System.arraycopy(out, 0, ret, 0, j);
       
   193         return ret;
       
   194     }
       
   195 
       
   196     /**
       
   197      * Specify which sources are visible to the compiler through -sourcepath.
       
   198      */
       
   199     public void setVisibleSources(Map<String,Source> vs) {
       
   200         visibleSrcs = new HashSet<URI>();
       
   201         for (String s : vs.keySet()) {
       
   202             Source src = vs.get(s);
       
   203             visibleSrcs.add(src.file().toURI());
       
   204         }
       
   205     }
       
   206 
       
   207     /**
       
   208      * Specify which classes are visible to the compiler through -classpath.
       
   209      */
       
   210     public void setVisibleClasses(Map<String,Source> vs) {
       
   211         visibleSrcs = new HashSet<URI>();
       
   212         for (String s : vs.keySet()) {
       
   213             Source src = vs.get(s);
       
   214             visibleSrcs.add(src.file().toURI());
       
   215         }
       
   216     }
       
   217     /**
       
   218      * Returns true if this is an incremental build.
       
   219      */
       
   220     public boolean isIncremental() {
       
   221         return !prev.sources().isEmpty();
       
   222     }
       
   223 
       
   224     /**
       
   225      * Find all artifacts that exists on disk.
       
   226      */
       
   227     public void findAllArtifacts() {
       
   228         binArtifacts = findAllFiles(binDir);
       
   229         gensrcArtifacts = findAllFiles(gensrcDir);
       
   230         headerArtifacts = findAllFiles(headerDir);
       
   231     }
       
   232 
       
   233     /**
       
   234      * Lookup the artifacts generated for this package in the previous build.
       
   235      */
       
   236     private Map<String,File> fetchPrevArtifacts(String pkg) {
       
   237         Package p = prev.packages().get(pkg);
       
   238         if (p != null) {
       
   239             return p.artifacts();
       
   240         }
       
   241         return new HashMap<String,File>();
       
   242     }
       
   243 
       
   244     /**
       
   245      * Delete all prev artifacts in the currently tainted packages.
       
   246      */
       
   247     public void deleteClassArtifactsInTaintedPackages() {
       
   248         for (String pkg : taintedPackages) {
       
   249             Map<String,File> arts = fetchPrevArtifacts(pkg);
       
   250             for (File f : arts.values()) {
       
   251                 if (f.exists() && f.getName().endsWith(".class")) {
       
   252                     f.delete();
       
   253                 }
       
   254             }
       
   255         }
       
   256     }
       
   257 
       
   258     /**
       
   259      * Mark the javac_state file to be in need of saving and as a side effect,
       
   260      * it gets a new timestamp.
       
   261      */
       
   262     private void needsSaving() {
       
   263         needsSaving = true;
       
   264     }
       
   265 
       
   266     /**
       
   267      * Save the javac_state file.
       
   268      */
       
   269     public void save() throws IOException {
       
   270         if (!needsSaving) return;
       
   271         try (FileWriter out = new FileWriter(javacStateFilename)) {
       
   272             StringBuilder b = new StringBuilder();
       
   273             long millisNow = System.currentTimeMillis();
       
   274             Date d = new Date(millisNow);
       
   275             SimpleDateFormat df =
       
   276                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
       
   277             b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n");
       
   278             b.append("# This format might change at any time. Please do not depend on it.\n");
       
   279             b.append("# M module\n");
       
   280             b.append("# P package\n");
       
   281             b.append("# S C source_tobe_compiled timestamp\n");
       
   282             b.append("# S L link_only_source timestamp\n");
       
   283             b.append("# G C generated_source timestamp\n");
       
   284             b.append("# A artifact timestamp\n");
       
   285             b.append("# D dependency\n");
       
   286             b.append("# I pubapi\n");
       
   287             b.append("# R arguments\n");
       
   288             b.append("R ").append(theArgs).append("\n");
       
   289 
       
   290             // Copy over the javac_state for the packages that did not need recompilation.
       
   291             now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>());
       
   292             // Save the packages, ie package names, dependencies, pubapis and artifacts!
       
   293             // I.e. the lot.
       
   294             Module.saveModules(now.modules(), b);
       
   295 
       
   296             String s = b.toString();
       
   297             out.write(s, 0, s.length());
       
   298         }
       
   299     }
       
   300 
       
   301     /**
       
   302      * Load a javac_state file.
       
   303      */
       
   304     public static JavacState load(String[] args, File binDir, File gensrcDir, File headerDir,
       
   305             boolean permitUnidentifiedArtifacts, PrintStream out, PrintStream err) {
       
   306         JavacState db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, false, out, err);
       
   307         Module  lastModule = null;
       
   308         Package lastPackage = null;
       
   309         Source  lastSource = null;
       
   310         boolean noFileFound = false;
       
   311         boolean foundCorrectVerNr = false;
       
   312         boolean newCommandLine = false;
       
   313         boolean syntaxError = false;
       
   314 
       
   315         try (BufferedReader in = new BufferedReader(new FileReader(db.javacStateFilename))) {
       
   316             for (;;) {
       
   317                 String l = in.readLine();
       
   318                 if (l==null) break;
       
   319                 if (l.length()>=3 && l.charAt(1) == ' ') {
       
   320                     char c = l.charAt(0);
       
   321                     if (c == 'M') {
       
   322                         lastModule = db.prev.loadModule(l);
       
   323                     } else
       
   324                     if (c == 'P') {
       
   325                         if (lastModule == null) { syntaxError = true; break; }
       
   326                         lastPackage = db.prev.loadPackage(lastModule, l);
       
   327                     } else
       
   328                     if (c == 'D') {
       
   329                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
       
   330                         lastPackage.loadDependency(l);
       
   331                     } else
       
   332                     if (c == 'I') {
       
   333                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
       
   334                         lastPackage.loadPubapi(l);
       
   335                     } else
       
   336                     if (c == 'A') {
       
   337                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
       
   338                         lastPackage.loadArtifact(l);
       
   339                     } else
       
   340                     if (c == 'S') {
       
   341                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
       
   342                         lastSource = db.prev.loadSource(lastPackage, l, false);
       
   343                     } else
       
   344                     if (c == 'G') {
       
   345                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
       
   346                         lastSource = db.prev.loadSource(lastPackage, l, true);
       
   347                     } else
       
   348                     if (c == 'R') {
       
   349                         String ncmdl = "R "+db.theArgs;
       
   350                         if (!l.equals(ncmdl)) {
       
   351                             newCommandLine = true;
       
   352                         }
       
   353                     } else
       
   354                          if (c == '#') {
       
   355                         if (l.startsWith("# javac_state ver ")) {
       
   356                             int sp = l.indexOf(" ", 18);
       
   357                             if (sp != -1) {
       
   358                                 String ver = l.substring(18,sp);
       
   359                                 if (!ver.equals("0.3")) {
       
   360                     break;
       
   361                                  }
       
   362                 foundCorrectVerNr = true;
       
   363                             }
       
   364                         }
       
   365                     }
       
   366                 }
       
   367             }
       
   368         } catch (FileNotFoundException e) {
       
   369             // Silently create a new javac_state file.
       
   370             noFileFound = true;
       
   371         } catch (IOException e) {
       
   372             Log.info("Dropping old javac_state because of errors when reading it.");
       
   373             db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
       
   374             foundCorrectVerNr = true;
       
   375             newCommandLine = false;
       
   376             syntaxError = false;
       
   377     }
       
   378         if (foundCorrectVerNr == false && !noFileFound) {
       
   379             Log.info("Dropping old javac_state since it is of an old version.");
       
   380             db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
       
   381         } else
       
   382         if (newCommandLine == true && !noFileFound) {
       
   383             Log.info("Dropping old javac_state since a new command line is used!");
       
   384             db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
       
   385         } else
       
   386         if (syntaxError == true) {
       
   387             Log.info("Dropping old javac_state since it contains syntax errors.");
       
   388             db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
       
   389         }
       
   390         db.prev.calculateDependents();
       
   391         return db;
       
   392     }
       
   393 
       
   394     /**
       
   395      * Mark a java package as tainted, ie it needs recompilation.
       
   396      */
       
   397     public void taintPackage(String name, String because) {
       
   398         if (!taintedPackages.contains(name)) {
       
   399             if (because != null) Log.debug("Tainting "+Util.justPackageName(name)+" because "+because);
       
   400             // It has not been tainted before.
       
   401             taintedPackages.add(name);
       
   402             needsSaving();
       
   403             Package nowp = now.packages().get(name);
       
   404             if (nowp != null) {
       
   405                 for (String d : nowp.dependents()) {
       
   406                     taintPackage(d, because);
       
   407                 }
       
   408             }
       
   409         }
       
   410     }
       
   411 
       
   412     /**
       
   413      * This packages need recompilation.
       
   414      */
       
   415     public Set<String> taintedPackages() {
       
   416         return taintedPackages;
       
   417     }
       
   418 
       
   419     /**
       
   420      * Clean out the tainted package set, used after the first round of compiles,
       
   421      * prior to propagating dependencies.
       
   422      */
       
   423     public void clearTaintedPackages() {
       
   424         taintedPackages = new HashSet<String>();
       
   425     }
       
   426 
       
   427     /**
       
   428      * Go through all sources and check which have been removed, added or modified
       
   429      * and taint the corresponding packages.
       
   430      */
       
   431     public void checkSourceStatus(boolean check_gensrc) {
       
   432         removedSources = calculateRemovedSources();
       
   433         for (Source s : removedSources) {
       
   434             if (!s.isGenerated() || check_gensrc) {
       
   435                 taintPackage(s.pkg().name(), "source "+s.name()+" was removed");
       
   436             }
       
   437         }
       
   438 
       
   439         addedSources = calculateAddedSources();
       
   440         for (Source s : addedSources) {
       
   441             String msg = null;
       
   442             if (isIncremental()) {
       
   443                 // When building from scratch, there is no point
       
   444                 // printing "was added" for every file since all files are added.
       
   445                 // However for an incremental build it makes sense.
       
   446                 msg = "source "+s.name()+" was added";
       
   447             }
       
   448             if (!s.isGenerated() || check_gensrc) {
       
   449                 taintPackage(s.pkg().name(), msg);
       
   450             }
       
   451         }
       
   452 
       
   453         modifiedSources = calculateModifiedSources();
       
   454         for (Source s : modifiedSources) {
       
   455             if (!s.isGenerated() || check_gensrc) {
       
   456                 taintPackage(s.pkg().name(), "source "+s.name()+" was modified");
       
   457             }
       
   458         }
       
   459     }
       
   460 
       
   461     /**
       
   462      * Acquire the compile_java_packages suffix rule for .java files.
       
   463      */
       
   464     public Map<String,Transformer> getJavaSuffixRule() {
       
   465         Map<String,Transformer> sr = new HashMap<String,Transformer>();
       
   466         sr.put(".java", compileJavaPackages);
       
   467         return sr;
       
   468     }
       
   469 
       
   470     /**
       
   471      * Acquire the copying transform.
       
   472      */
       
   473     public Transformer getCopier() {
       
   474         return copyFiles;
       
   475     }
       
   476 
       
   477     /**
       
   478      * If artifacts have gone missing, force a recompile of the packages
       
   479      * they belong to.
       
   480      */
       
   481     public void taintPackagesThatMissArtifacts() {
       
   482         for (Package pkg : prev.packages().values()) {
       
   483             for (File f : pkg.artifacts().values()) {
       
   484                 if (!f.exists()) {
       
   485                     // Hmm, the artifact on disk does not exist! Someone has removed it....
       
   486                     // Lets rebuild the package.
       
   487                     taintPackage(pkg.name(), ""+f+" is missing.");
       
   488                 }
       
   489             }
       
   490         }
       
   491     }
       
   492 
       
   493     /**
       
   494      * Propagate recompilation through the dependency chains.
       
   495      * Avoid re-tainting packages that have already been compiled.
       
   496      */
       
   497     public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) {
       
   498         for (Package pkg : prev.packages().values()) {
       
   499             for (String dep : pkg.dependencies()) {
       
   500                 if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) {
       
   501                     taintPackage(pkg.name(), " its depending on "+dep);
       
   502                 }
       
   503             }
       
   504         }
       
   505     }
       
   506 
       
   507     /**
       
   508      * Scan all output dirs for artifacts and remove those files (artifacts?)
       
   509      * that are not recognized as such, in the javac_state file.
       
   510      */
       
   511     public void removeUnidentifiedArtifacts() {
       
   512         Set<File> allKnownArtifacts = new HashSet<File>();
       
   513         for (Package pkg : prev.packages().values()) {
       
   514             for (File f : pkg.artifacts().values()) {
       
   515                 allKnownArtifacts.add(f);
       
   516             }
       
   517         }
       
   518         // Do not forget about javac_state....
       
   519         allKnownArtifacts.add(javacState);
       
   520 
       
   521         for (File f : binArtifacts) {
       
   522             if (!allKnownArtifacts.contains(f)) {
       
   523                 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
       
   524                 f.delete();
       
   525             }
       
   526         }
       
   527         for (File f : headerArtifacts) {
       
   528             if (!allKnownArtifacts.contains(f)) {
       
   529                 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
       
   530                 f.delete();
       
   531             }
       
   532         }
       
   533         for (File f : gensrcArtifacts) {
       
   534             if (!allKnownArtifacts.contains(f)) {
       
   535                 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
       
   536                 f.delete();
       
   537             }
       
   538         }
       
   539     }
       
   540 
       
   541     /**
       
   542      * Remove artifacts that are no longer produced when compiling!
       
   543      */
       
   544     public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) {
       
   545         // Nothing to do, if nothing was recompiled.
       
   546         if (recentlyCompiled.size() == 0) return;
       
   547 
       
   548         for (String pkg : now.packages().keySet()) {
       
   549             // If this package has not been recompiled, skip the check.
       
   550             if (!recentlyCompiled.contains(pkg)) continue;
       
   551             Collection<File> arts = now.artifacts().values();
       
   552             for (File f : fetchPrevArtifacts(pkg).values()) {
       
   553                 if (!arts.contains(f)) {
       
   554                     Log.debug("Removing "+f.getPath()+" since it is now superfluous!");
       
   555                     if (f.exists()) f.delete();
       
   556                 }
       
   557             }
       
   558         }
       
   559     }
       
   560 
       
   561     /**
       
   562      * Return those files belonging to prev, but not now.
       
   563      */
       
   564     private Set<Source> calculateRemovedSources() {
       
   565         Set<Source> removed = new HashSet<Source>();
       
   566         for (String src : prev.sources().keySet()) {
       
   567             if (now.sources().get(src) == null) {
       
   568                 removed.add(prev.sources().get(src));
       
   569             }
       
   570         }
       
   571         return removed;
       
   572     }
       
   573 
       
   574     /**
       
   575      * Return those files belonging to now, but not prev.
       
   576      */
       
   577     private Set<Source> calculateAddedSources() {
       
   578         Set<Source> added = new HashSet<Source>();
       
   579         for (String src : now.sources().keySet()) {
       
   580             if (prev.sources().get(src) == null) {
       
   581                 added.add(now.sources().get(src));
       
   582             }
       
   583         }
       
   584         return added;
       
   585     }
       
   586 
       
   587     /**
       
   588      * Return those files where the timestamp is newer.
       
   589      * If a source file timestamp suddenly is older than what is known
       
   590      * about it in javac_state, then consider it modified, but print
       
   591      * a warning!
       
   592      */
       
   593     private Set<Source> calculateModifiedSources() {
       
   594         Set<Source> modified = new HashSet<Source>();
       
   595         for (String src : now.sources().keySet()) {
       
   596             Source n = now.sources().get(src);
       
   597             Source t = prev.sources().get(src);
       
   598             if (prev.sources().get(src) != null) {
       
   599                 if (t != null) {
       
   600                     if (n.lastModified() > t.lastModified()) {
       
   601                         modified.add(n);
       
   602                     } else if (n.lastModified() < t.lastModified()) {
       
   603                         modified.add(n);
       
   604                         Log.warn("The source file "+n.name()+" timestamp has moved backwards in time.");
       
   605                     }
       
   606                 }
       
   607             }
       
   608         }
       
   609         return modified;
       
   610     }
       
   611 
       
   612     /**
       
   613      * Recursively delete a directory and all its contents.
       
   614      */
       
   615     private static void deleteContents(File dir) {
       
   616         if (dir != null && dir.exists()) {
       
   617             for (File f : dir.listFiles()) {
       
   618                 if (f.isDirectory()) {
       
   619                     deleteContents(f);
       
   620                 }
       
   621                 f.delete();
       
   622             }
       
   623         }
       
   624     }
       
   625 
       
   626     /**
       
   627      * Run the copy translator only.
       
   628      */
       
   629     public void performCopying(File binDir, Map<String,Transformer> suffixRules) {
       
   630         Map<String,Transformer> sr = new HashMap<String,Transformer>();
       
   631         for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) {
       
   632             if (e.getValue() == copyFiles) {
       
   633                 sr.put(e.getKey(), e.getValue());
       
   634             }
       
   635         }
       
   636         perform(binDir, sr);
       
   637     }
       
   638 
       
   639     /**
       
   640      * Run all the translators that translate into java source code.
       
   641      * I.e. all translators that are not copy nor compile_java_source.
       
   642      */
       
   643     public void performTranslation(File gensrcDir, Map<String,Transformer> suffixRules) {
       
   644         Map<String,Transformer> sr = new HashMap<String,Transformer>();
       
   645         for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) {
       
   646             if (e.getValue() != copyFiles &&
       
   647                 e.getValue() != compileJavaPackages) {
       
   648                 sr.put(e.getKey(), e.getValue());
       
   649             }
       
   650         }
       
   651         perform(gensrcDir, sr);
       
   652     }
       
   653 
       
   654     /**
       
   655      * Compile all the java sources. Return true, if it needs to be called again!
       
   656      */
       
   657     public boolean performJavaCompilations(File binDir,
       
   658                                            String serverSettings,
       
   659                                            String[] args,
       
   660                                            Set<String> recentlyCompiled,
       
   661                                            boolean[] rcValue) {
       
   662         Map<String,Transformer> suffixRules = new HashMap<String,Transformer>();
       
   663         suffixRules.put(".java", compileJavaPackages);
       
   664         compileJavaPackages.setExtra(serverSettings);
       
   665         compileJavaPackages.setExtra(args);
       
   666 
       
   667         rcValue[0] = perform(binDir, suffixRules);
       
   668         recentlyCompiled.addAll(taintedPackages());
       
   669         clearTaintedPackages();
       
   670         boolean again = !packagesWithChangedPublicApis.isEmpty();
       
   671         taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled);
       
   672         packagesWithChangedPublicApis = new HashSet<String>();
       
   673         return again && rcValue[0];
       
   674     }
       
   675 
       
   676     /**
       
   677      * Store the source into the set of sources belonging to the given transform.
       
   678      */
       
   679     private void addFileToTransform(Map<Transformer,Map<String,Set<URI>>> gs, Transformer t, Source s) {
       
   680         Map<String,Set<URI>> fs = gs.get(t);
       
   681         if (fs == null) {
       
   682             fs = new HashMap<String,Set<URI>>();
       
   683             gs.put(t, fs);
       
   684         }
       
   685         Set<URI> ss = fs.get(s.pkg().name());
       
   686         if (ss == null) {
       
   687             ss = new HashSet<URI>();
       
   688             fs.put(s.pkg().name(), ss);
       
   689         }
       
   690         ss.add(s.file().toURI());
       
   691     }
       
   692 
       
   693     /**
       
   694      * For all packages, find all sources belonging to the package, group the sources
       
   695      * based on their transformers and apply the transformers on each source code group.
       
   696      */
       
   697     private boolean perform(File outputDir, Map<String,Transformer> suffixRules)
       
   698     {
       
   699         boolean rc = true;
       
   700         // Group sources based on transforms. A source file can only belong to a single transform.
       
   701         Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<Transformer,Map<String,Set<URI>>>();
       
   702         for (Source src : now.sources().values()) {
       
   703             Transformer t = suffixRules.get(src.suffix());
       
   704                if (t != null) {
       
   705                 if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) {
       
   706                     addFileToTransform(groupedSources, t, src);
       
   707                 }
       
   708             }
       
   709         }
       
   710         // Go through the transforms and transform them.
       
   711         for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) {
       
   712             Transformer t = e.getKey();
       
   713             Map<String,Set<URI>> srcs = e.getValue();
       
   714             // These maps need to be synchronized since multiple threads will be writing results into them.
       
   715             Map<String,Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<String,Set<URI>>());
       
   716             Map<String,Set<String>> packageDependencies = Collections.synchronizedMap(new HashMap<String,Set<String>>());
       
   717             Map<String,String> packagePublicApis = Collections.synchronizedMap(new HashMap<String,String>());
       
   718 
       
   719             boolean  r = t.transform(srcs,
       
   720                                      visibleSrcs,
       
   721                                      visibleClasses,
       
   722                                      prev.dependents(),
       
   723                                      outputDir.toURI(),
       
   724                                      packageArtifacts,
       
   725                                      packageDependencies,
       
   726                                      packagePublicApis,
       
   727                                      0,
       
   728                                      isIncremental(),
       
   729                                      numCores,
       
   730                                      out,
       
   731                                      err);
       
   732             if (!r) rc = false;
       
   733 
       
   734             for (String p : srcs.keySet()) {
       
   735                 recompiledPackages.add(p);
       
   736             }
       
   737             // The transform is done! Extract all the artifacts and store the info into the Package objects.
       
   738             for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) {
       
   739                 Module mnow = now.findModuleFromPackageName(a.getKey());
       
   740                 mnow.addArtifacts(a.getKey(), a.getValue());
       
   741             }
       
   742             // Extract all the dependencies and store the info into the Package objects.
       
   743             for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) {
       
   744                 Set<String> deps = a.getValue();
       
   745                 Module mnow = now.findModuleFromPackageName(a.getKey());
       
   746                 mnow.setDependencies(a.getKey(), deps);
       
   747             }
       
   748             // Extract all the pubapis and store the info into the Package objects.
       
   749             for (Map.Entry<String,String> a : packagePublicApis.entrySet()) {
       
   750                 Module mprev = prev.findModuleFromPackageName(a.getKey());
       
   751                 List<String> pubapi = Package.pubapiToList(a.getValue());
       
   752                 Module mnow = now.findModuleFromPackageName(a.getKey());
       
   753                 mnow.setPubapi(a.getKey(), pubapi);
       
   754                 if (mprev.hasPubapiChanged(a.getKey(), pubapi)) {
       
   755                     // Aha! The pubapi of this package has changed!
       
   756                     // It can also be a new compile from scratch.
       
   757                     if (mprev.lookupPackage(a.getKey()).existsInJavacState()) {
       
   758                         // This is an incremental compile! The pubapi
       
   759                         // did change. Trigger recompilation of dependents.
       
   760                         packagesWithChangedPublicApis.add(a.getKey());
       
   761                         Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!");
       
   762                     }
       
   763                 }
       
   764             }
       
   765         }
       
   766         return rc;
       
   767     }
       
   768 
       
   769     /**
       
   770      * Utility method to recursively find all files below a directory.
       
   771      */
       
   772     private static Set<File> findAllFiles(File dir) {
       
   773         Set<File> foundFiles = new HashSet<File>();
       
   774         if (dir == null) {
       
   775             return foundFiles;
       
   776         }
       
   777         recurse(dir, foundFiles);
       
   778         return foundFiles;
       
   779     }
       
   780 
       
   781     private static void recurse(File dir, Set<File> foundFiles) {
       
   782         for (File f : dir.listFiles()) {
       
   783             if (f.isFile()) {
       
   784                 foundFiles.add(f);
       
   785             } else if (f.isDirectory()) {
       
   786                 recurse(f, foundFiles);
       
   787             }
       
   788         }
       
   789     }
       
   790 
       
   791     /**
       
   792      * Compare the calculate source list, with an explicit list, usually supplied from the makefile.
       
   793      * Used to detect bugs where the makefile and sjavac have different opinions on which files
       
   794      * should be compiled.
       
   795      */
       
   796     public void compareWithMakefileList(File makefileSourceList)
       
   797             throws ProblemException
       
   798     {
       
   799         // If we are building on win32 using for example cygwin the paths in the makefile source list
       
   800         // might be /cygdrive/c/.... which does not match c:\....
       
   801         // We need to adjust our calculated sources to be identical, if necessary.
       
   802         boolean mightNeedRewriting = File.pathSeparatorChar == ';';
       
   803 
       
   804         if (makefileSourceList == null) return;
       
   805 
       
   806         Set<String> calculatedSources = new HashSet<String>();
       
   807         Set<String> listedSources = new HashSet<String>();
       
   808 
       
   809         // Create a set of filenames with full paths.
       
   810         for (Source s : now.sources().values()) {
       
   811             calculatedSources.add(s.file().getPath());
       
   812         }
       
   813         // Read in the file and create another set of filenames with full paths.
       
   814         try {
       
   815             BufferedReader in = new BufferedReader(new FileReader(makefileSourceList));
       
   816             for (;;) {
       
   817                 String l = in.readLine();
       
   818                 if (l==null) break;
       
   819                 l = l.trim();
       
   820                 if (mightNeedRewriting) {
       
   821                     if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) {
       
   822                         // Everything a-ok, the format is already C:\foo\bar
       
   823                     } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) {
       
   824                         // The format is C:/foo/bar, rewrite into the above format.
       
   825                         l = l.replaceAll("/","\\\\");
       
   826                     } else if (l.charAt(0) == '/' && l.indexOf("/",1) != -1) {
       
   827                         // The format might be: /cygdrive/c/foo/bar, rewrite into the above format.
       
   828                         // Do not hardcode the name cygdrive here.
       
   829                         int slash = l.indexOf("/",1);
       
   830                         l = l.replaceAll("/","\\\\");
       
   831                         l = ""+l.charAt(slash+1)+":"+l.substring(slash+2);
       
   832                     }
       
   833                     if (Character.isLowerCase(l.charAt(0))) {
       
   834                         l = Character.toUpperCase(l.charAt(0))+l.substring(1);
       
   835                     }
       
   836                 }
       
   837                 listedSources.add(l);
       
   838             }
       
   839         } catch (FileNotFoundException e) {
       
   840             throw new ProblemException("Could not open "+makefileSourceList.getPath()+" since it does not exist!");
       
   841         } catch (IOException e) {
       
   842             throw new ProblemException("Could not read "+makefileSourceList.getPath());
       
   843         }
       
   844 
       
   845         for (String s : listedSources) {
       
   846             if (!calculatedSources.contains(s)) {
       
   847                  throw new ProblemException("The makefile listed source "+s+" was not calculated by the smart javac wrapper!");
       
   848             }
       
   849         }
       
   850 
       
   851         for (String s : calculatedSources) {
       
   852             if (!listedSources.contains(s)) {
       
   853                 throw new ProblemException("The smart javac wrapper calculated source "+s+" was not listed by the makefiles!");
       
   854             }
       
   855         }
       
   856     }
       
   857 }