1 /* 2 * Copyright (c) 2012, 2014, 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.List; 35 import java.util.Map; 36 import java.util.Set; 37 import java.util.TreeMap; 38 import java.util.regex.Matcher; 39 import java.util.regex.Pattern; 40 import java.util.stream.Stream; 41 42 import com.sun.tools.javac.util.Assert; 43 import com.sun.tools.sjavac.pubapi.PubApi; 44 45 /** 46 * The Package class maintains meta information about a package. 47 * For example its sources, dependents,its pubapi and its artifacts. 48 * 49 * It might look odd that we track dependents/pubapi/artifacts on 50 * a package level, but it makes sense since recompiling a full package 51 * takes as long as recompiling a single java file in that package, 52 * if you take into account the startup time of the jvm. 53 * 54 * Also the dependency information will be much smaller (good for the javac_state file size) 55 * and it simplifies tracking artifact generation, you do not always know from which 56 * source a class file was generated, but you always know which package it belongs to. 57 * 58 * It is also educational to see package dependencies triggering recompilation of 59 * other packages. Even though the recompilation was perhaps not necessary, 60 * the visible recompilation of the dependent packages indicates how much circular 61 * dependencies your code has. 62 * 63 * <p><b>This is NOT part of any supported API. 64 * If you write code that depends on this, you do so at your own risk. 65 * This code and its internal interfaces are subject to change or 66 * deletion without notice.</b> 67 */ 68 public class Package implements Comparable<Package> { 69 // The module this package belongs to. (There is a legacy module with an empty string name, 70 // used for all legacy sources.) 71 private Module mod; 72 // Name of this package, module:pkg 73 // ex1 jdk.base:java.lang 74 // ex2 :java.lang (when in legacy mode) 75 private String name; 76 // The directory path to the package. If the package belongs to a module, 77 // then that module's file system name is part of the path. 78 private String dirname; 79 // This package has the following dependents, that depend on this package. 80 private Set<String> dependents = new HashSet<>(); 81 82 // Fully qualified name of class in this package -> fully qualified name of dependency 83 private Map<String, Set<String>> dependencies = new TreeMap<>(); 84 // Fully qualified name of class in this package -> fully qualified name of dependency on class path 85 private Map<String, Set<String>> cpDependencies = new TreeMap<>(); 86 87 // This is the public api of this package. 88 private PubApi pubApi = new PubApi(); 89 // Map from source file name to Source info object. 90 private Map<String,Source> sources = new HashMap<>(); 91 // This package generated these artifacts. 92 private Map<String,File> artifacts = new HashMap<>(); 93 94 public Package(Module m, String n) { 95 int c = n.indexOf(":"); 96 Assert.check(c != -1); 97 Assert.check(m.name().equals(m.name())); 98 name = n; 99 dirname = n.replace('.', File.separatorChar); 100 if (m.name().length() > 0) { 101 // There is a module here, prefix the module dir name to the path. 102 dirname = m.dirname()+File.separatorChar+dirname; 103 } 104 } 105 106 public Module mod() { return mod; } 107 public String name() { return name; } 108 public String dirname() { return dirname; } 109 public Map<String,Source> sources() { return sources; } 110 public Map<String,File> artifacts() { return artifacts; } 111 public PubApi getPubApi() { return pubApi; } 112 113 public Map<String,Set<String>> typeDependencies() { return dependencies; } 114 public Map<String,Set<String>> typeClasspathDependencies() { return cpDependencies; } 115 116 public Set<String> dependents() { return dependents; } 117 118 @Override 119 public boolean equals(Object o) { 120 return (o instanceof Package) && name.equals(((Package)o).name); 121 } 122 123 @Override 124 public int hashCode() { 125 return name.hashCode(); 126 } 127 128 @Override 129 public int compareTo(Package o) { 130 return name.compareTo(o.name); 131 } 132 133 public void addSource(Source s) { 134 sources.put(s.file().getPath(), s); 135 } 136 137 private static Pattern DEP_PATTERN = Pattern.compile("(.*) -> (.*)"); 138 public void parseAndAddDependency(String d, boolean cp) { 139 Matcher m = DEP_PATTERN.matcher(d); 140 if (!m.matches()) 141 throw new IllegalArgumentException("Bad dependency string: " + d); 142 addDependency(m.group(1), m.group(2), cp); 143 } 144 145 public void addDependency(String fullyQualifiedFrom, 146 String fullyQualifiedTo, 147 boolean cp) { 148 Map<String, Set<String>> map = cp ? cpDependencies : dependencies; 149 if (!map.containsKey(fullyQualifiedFrom)) 150 map.put(fullyQualifiedFrom, new HashSet<>()); 151 map.get(fullyQualifiedFrom).add(fullyQualifiedTo); 152 } 153 154 public void addDependent(String d) { 155 dependents.add(d); 156 } 157 158 /** 159 * Check if we have knowledge in the javac state that 160 * describe the results of compiling this package before. 161 */ 162 public boolean existsInJavacState() { 163 return artifacts.size() > 0 || !pubApi.isEmpty(); 164 } 165 166 public boolean hasPubApiChanged(PubApi newPubApi) { 167 return !newPubApi.isBackwardCompatibleWith(pubApi); 168 } 169 170 public void setPubapi(PubApi newPubApi) { 171 pubApi = newPubApi; 172 } 173 174 public void setDependencies(Map<String, Set<String>> ds, boolean cp) { 175 (cp ? cpDependencies : dependencies).clear(); 176 for (String fullyQualifiedFrom : ds.keySet()) 177 for (String fullyQualifiedTo : ds.get(fullyQualifiedFrom)) 178 addDependency(fullyQualifiedFrom, fullyQualifiedTo, cp); 179 } 180 181 public void save(StringBuilder b) { 182 b.append("P ").append(name).append("\n"); 183 Source.saveSources(sources, b); 184 saveDependencies(b); 185 savePubapi(b); 186 saveArtifacts(b); 187 } 188 189 static public Package load(Module module, String l) { 190 String name = l.substring(2); 191 return new Package(module, name); 192 } 193 194 public void saveDependencies(StringBuilder b) { 195 196 // Dependencies where *to* is among sources 197 for (String fullyQualifiedFrom : dependencies.keySet()) { 198 for (String fullyQualifiedTo : dependencies.get(fullyQualifiedFrom)) { 199 b.append(String.format("D S %s -> %s%n", fullyQualifiedFrom, fullyQualifiedTo)); 200 } 201 } 202 203 // Dependencies where *to* is on class path 204 for (String fullyQualifiedFrom : cpDependencies.keySet()) { 205 for (String fullyQualifiedTo : cpDependencies.get(fullyQualifiedFrom)) { 206 b.append(String.format("D C %s -> %s%n", fullyQualifiedFrom, fullyQualifiedTo)); 207 } 208 } 209 } 210 211 public void savePubapi(StringBuilder b) { 212 pubApi.asListOfStrings() 213 .stream() 214 .flatMap(l -> Stream.of("I ", l, "\n")) 215 .forEach(b::append); 216 } 217 218 public static void savePackages(Map<String,Package> packages, StringBuilder b) { 219 List<String> sorted_packages = new ArrayList<>(); 220 for (String key : packages.keySet() ) { 221 sorted_packages.add(key); 222 } 223 Collections.sort(sorted_packages); 224 for (String s : sorted_packages) { 225 Package p = packages.get(s); 226 p.save(b); 227 } 228 } 229 230 public void addArtifact(String a) { 231 artifacts.put(a, new File(a)); 232 } 233 234 public void addArtifact(File f) { 235 artifacts.put(f.getPath(), f); 236 } 237 238 public void addArtifacts(Set<URI> as) { 239 for (URI u : as) { 240 addArtifact(new File(u)); 241 } 242 } 243 244 public void setArtifacts(Set<URI> as) { 245 Assert.check(!artifacts.isEmpty()); 246 artifacts = new HashMap<>(); 247 addArtifacts(as); 248 } 249 250 public void loadArtifact(String l) { 251 // Find next space after "A ". 252 int dp = l.indexOf(' ',2); 253 String fn = l.substring(2,dp); 254 long last_modified = Long.parseLong(l.substring(dp+1)); 255 File f = new File(fn); 256 if (f.exists() && f.lastModified() != last_modified) { 257 // Hmm, the artifact on disk does not have the same last modified 258 // timestamp as the information from the build database. 259 // We no longer trust the artifact on disk. Delete it. 260 // The smart javac wrapper will then rebuild the artifact. 261 Log.debug("Removing "+f.getPath()+" since its timestamp does not match javac_state."); 262 f.delete(); 263 } 264 artifacts.put(f.getPath(), f); 265 } 266 267 public void saveArtifacts(StringBuilder b) { 268 List<File> sorted_artifacts = new ArrayList<>(); 269 for (File f : artifacts.values()) { 270 sorted_artifacts.add(f); 271 } 272 Collections.sort(sorted_artifacts); 273 for (File f : sorted_artifacts) { 274 // The last modified information is only used 275 // to detect tampering with the output dir. 276 // If the outputdir has been modified, not by javac, 277 // then a mismatch will be detected in the last modified 278 // timestamps stored in the build database compared 279 // to the timestamps on disk and the artifact will be deleted. 280 281 b.append("A "+f.getPath()+" "+f.lastModified()+"\n"); 282 } 283 } 284 285 /** 286 * Always clean out a tainted package before it is recompiled. 287 */ 288 public void deleteArtifacts() { 289 for (File a : artifacts.values()) { 290 a.delete(); 291 } 292 } 293 }