1 /*
   2  * Copyright (c) 2012, 2013, 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.File;
  29 import java.net.URI;
  30 import java.util.ArrayList;
  31 import java.util.Collections;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.Iterator;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.Set;
  38 
  39 /**
  40  * The Package class maintains meta information about a package.
  41  * For example its sources, dependents,its pubapi and its artifacts.
  42  *
  43  * It might look odd that we track dependents/pubapi/artifacts on
  44  * a package level, but it makes sense since recompiling a full package
  45  * takes as long as recompiling a single java file in that package,
  46  * if you take into account the startup time of the jvm.
  47  *
  48  * Also the dependency information will be much smaller (good for the javac_state file size)
  49  * and it simplifies tracking artifact generation, you do not always know from which
  50  * source a class file was generated, but you always know which package it belongs to.
  51  *
  52  * It is also educational to see package dependencies triggering recompilation of
  53  * other packages. Even though the recompilation was perhaps not necessary,
  54  * the visible recompilation of the dependent packages indicates how much circular
  55  * dependencies your code has.
  56  *
  57  * <p><b>This is NOT part of any supported API.
  58  * If you write code that depends on this, you do so at your own
  59  * risk.  This code and its internal interfaces are subject to change
  60  * or deletion without notice.</b></p>
  61  */
  62 public class Package implements Comparable<Package> {
  63     // The module this package belongs to. (There is a legacy module with an empty string name,
  64     // used for all legacy sources.)
  65     private Module mod;
  66     // Name of this package, module:pkg
  67     // ex1 jdk.base:java.lang
  68     // ex2 :java.lang (when in legacy mode)
  69     private String name;
  70     // The directory path to the package. If the package belongs to a module,
  71     // then that module's file system name is part of the path.
  72     private String dirname;
  73     // This package depends on these packages.
  74     private Set<String> dependencies = new HashSet<>();
  75     // This package has the following dependents, that depend on this package.
  76     private Set<String> dependents = new HashSet<>();
  77     // Map from source file name to Source info object.
  78     private Map<String,Source> sources = new HashMap<>();
  79     // This package generated these artifacts.
  80     private Map<String,File> artifacts = new HashMap<>();
  81     // Pubapi for compiled sources
  82     private List<String> pubapi_for_compiled_sources = new ArrayList<>();
  83     // Pubapi for linked classes
  84     private List<String> pubapi_for_linked_classes = new ArrayList<>();
  85     // Archives that have the same timestamp as previous run, ie they are probably unchanged.
  86     private Set<String> unchanged_archives = new HashSet<>();
  87 
  88     public Package(Module m, String n) {
  89         int c = n.indexOf(":");
  90         assert(c != -1);
  91         String mn = n.substring(0,c);
  92         assert(m.name().equals(m.name()));
  93         name = n;
  94         dirname = n.replace('.', File.separatorChar);
  95         if (m.name().length() > 0) {
  96             // There is a module here, prefix the module dir name to the path.
  97             dirname = m.dirname()+File.separatorChar+dirname;
  98         }
  99     }
 100 
 101     public Module mod() { return mod; }
 102     public String name() { return name; }
 103     public String dirname() { return dirname; }
 104     public Map<String,Source> sources() { return sources; }
 105     public Map<String,File> artifacts() { return artifacts; }
 106     public List<String> pubapiForCompiledSources() { return pubapi_for_compiled_sources; }
 107     public List<String> pubapiForLinkedClasses() { return pubapi_for_linked_classes; }
 108 
 109     public Set<String> dependencies() { return dependencies; }
 110     public Set<String> dependents() { return dependents; }
 111 
 112     @Override
 113     public boolean equals(Object o) {
 114         return (o instanceof Package) && name.equals(((Package)o).name);
 115     }
 116 
 117     @Override
 118     public int hashCode() {
 119         return name.hashCode();
 120     }
 121 
 122     @Override
 123     public int compareTo(Package o) {
 124         return name.compareTo(o.name);
 125     }
 126 
 127     public void addSource(Source s) {
 128         sources.put(s.file().getPath(), s);
 129     }
 130 
 131     public void addDependency(String d) {
 132         dependencies.add(d);
 133     }
 134 
 135     public void addDependent(String d) {
 136         dependents.add(d);
 137     }
 138 
 139     /**
 140      * Check if we have knowledge in the javac state that
 141      * describe the results of compiling this package before.
 142      */
 143     public boolean existsInJavacState() {
 144         return artifacts.size() > 0 || pubapi_for_compiled_sources.size() > 0;
 145     }
 146 
 147     public boolean hasPubapiForCompiledSourcesChanged(List<String> ps) {
 148         Iterator<String> i = ps.iterator();
 149         Iterator<String> j = pubapi_for_compiled_sources.iterator();
 150         int line = 0;
 151         while (i.hasNext() && j.hasNext()) {
 152             String is = i.next();
 153             String js = j.next();
 154             if (!is.equals(js)) {
 155                 Log.debug("Change in pubapi for package "+name+" line "+line);
 156                 Log.debug("Old: "+js);
 157                 Log.debug("New: "+is);
 158                 return true;
 159             }
 160             line++;
 161         }
 162         if ((i.hasNext() && !j.hasNext() ) ||
 163             (!i.hasNext() && j.hasNext())) {
 164             Log.debug("Change in pubapi for package "+name);
 165             if (i.hasNext()) {
 166                 Log.debug("New has more lines!");
 167             } else {
 168                 Log.debug("Old has more lines!");
 169             }
 170             return true;
 171         }
 172         return false;
 173     }
 174 
 175     public void setPubapiForCompiledSources(List<String> ps) {
 176         pubapi_for_compiled_sources = ps;
 177     }
 178 
 179     public void setPubapiForLinkedClasses(List<String> ps) {
 180         pubapi_for_linked_classes = ps;
 181     }
 182 
 183     public void setDependencies(Set<String> ds) {
 184         dependencies = ds;
 185     }
 186 
 187     public void save(StringBuilder b) {
 188         b.append("P ").append(name).append("\n");
 189         Source.saveSources(sources, b);
 190         saveDependencies(b);
 191         savePubapi(b);
 192         saveArtifacts(b);
 193     }
 194 
 195     static public Package load(Module module, String l) {
 196         String name = l.substring(2);
 197         return new Package(module, name);
 198     }
 199 
 200     public void loadDependency(String l) {
 201         String n = l.substring(2);
 202         addDependency(n);
 203     }
 204 
 205     public void loadPubapi(String l) {
 206         char c = l.charAt(2);
 207         String pi = l.substring(4);
 208         switch (c) {
 209         case 'C' :  pubapi_for_compiled_sources.add(pi);
 210             break;
 211         case 'Z' :  pubapi_for_linked_classes.add(pi);
 212             break;
 213         }
 214     }
 215 
 216     public void saveDependencies(StringBuilder b) {
 217         List<String> sorted_dependencies = new ArrayList<>();
 218         for (String key : dependencies) {
 219             sorted_dependencies.add(key);
 220         }
 221         Collections.sort(sorted_dependencies);
 222         for (String a : sorted_dependencies) {
 223             b.append("D "+a+"\n");
 224         }
 225     }
 226 
 227     public void savePubapi(StringBuilder b) {
 228         for (String l : pubapi_for_compiled_sources) {
 229             b.append("I C "+l+"\n");
 230         }
 231         for (String l : pubapi_for_linked_classes) {
 232             b.append("I Z "+l+"\n");
 233         }
 234     }
 235 
 236     public static void savePackages(Map<String,Package> packages, StringBuilder b) {
 237         List<String> sorted_source_packages = new ArrayList<>();
 238         // First add all packages that have components from from our own sources.
 239         for (String key : packages.keySet() ) {
 240             Package p = packages.get(key);
 241             if (p.sources().size() > 0) {
 242                 sorted_source_packages.add(key);
 243         }
 244         }
 245         Collections.sort(sorted_source_packages);
 246 
 247         List<String> sorted_classpath_packages = new ArrayList<>();
 248         // Second add all packages found on the classpath only.
 249         for (String key : packages.keySet() ) {
 250             Package p = packages.get(key);
 251             if (p.sources().size() == 0) {
 252                 sorted_classpath_packages.add(key);
 253             }
 254         }
 255         Collections.sort(sorted_classpath_packages);
 256 
 257         for (String s : sorted_source_packages) {
 258             Package p = packages.get(s);
 259             p.save(b);
 260         }
 261         for (String s : sorted_classpath_packages) {
 262             Package p = packages.get(s);
 263             p.save(b);
 264         }
 265     }
 266 
 267     public void addArtifact(String a) {
 268         artifacts.put(a, new File(a));
 269     }
 270 
 271     public void addArtifact(File f) {
 272         artifacts.put(f.getPath(), f);
 273     }
 274 
 275     public void addArtifacts(Set<URI> as) {
 276         for (URI u : as) {
 277             addArtifact(new File(u));
 278         }
 279     }
 280 
 281     public void setArtifacts(Set<URI> as) {
 282         assert(!artifacts.isEmpty());
 283         artifacts = new HashMap<>();
 284         addArtifacts(as);
 285     }
 286 
 287     public void loadArtifact(String l) {
 288         // Find next space after "A ".
 289         int dp = l.indexOf(' ',2);
 290         String fn = l.substring(2,dp);
 291         long last_modified = Long.parseLong(l.substring(dp+1));
 292         File f = new File(fn);
 293         if (f.exists() && f.lastModified() != last_modified) {
 294             // Hmm, the artifact on disk does not have the same last modified
 295             // timestamp as the information from the build database.
 296             // We no longer trust the artifact on disk. Delete it.
 297             // The smart javac wrapper will then rebuild the artifact.
 298             Log.debug("Removing "+f.getPath()+" since its timestamp does not match javac_state.");
 299             f.delete();
 300         }
 301         artifacts.put(f.getPath(), f);
 302     }
 303 
 304     public void saveArtifacts(StringBuilder b) {
 305         List<File> sorted_artifacts = new ArrayList<>();
 306         for (File f : artifacts.values()) {
 307             sorted_artifacts.add(f);
 308         }
 309         Collections.sort(sorted_artifacts);
 310         for (File f : sorted_artifacts) {
 311             // The last modified information is only used
 312             // to detect tampering with the output dir.
 313             // If the outputdir has been modified, not by javac,
 314             // then a mismatch will be detected in the last modified
 315             // timestamps stored in the build database compared
 316             // to the timestamps on disk and the artifact will be deleted.
 317 
 318             b.append("A "+f.getPath()+" "+f.lastModified()+"\n");
 319         }
 320     }
 321 
 322     /**
 323      * Always clean out a tainted package before it is recompiled.
 324      */
 325     public void deleteArtifacts() {
 326         for (File a : artifacts.values()) {
 327             a.delete();
 328         }
 329     }
 330 
 331     /**
 332      * Extract the classes stored in the pubapi.
 333      */
 334     public Set<String> getClassesFromClasspathPubapi() {
 335         Set<String> set = new HashSet<String>();
 336 
 337         for (String s : pubapi_for_linked_classes) {
 338             if (s.startsWith(" TYPE ")) {
 339                 set.add(s.substring(6, s.indexOf(' ', 6)));
 340             }
 341         }
 342         return set;
 343     }
 344 
 345 
 346 }