1 /*
2  * Copyright (c) 2012, 2015, 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 
Package(Module m, String n)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 
mod()106     public Module mod() { return mod; }
name()107     public String name() { return name; }
dirname()108     public String dirname() { return dirname; }
sources()109     public Map<String,Source> sources() { return sources; }
artifacts()110     public Map<String,File> artifacts() { return artifacts; }
getPubApi()111     public PubApi getPubApi() { return pubApi; }
112 
typeDependencies()113     public Map<String,Set<String>> typeDependencies() { return dependencies; }
typeClasspathDependencies()114     public Map<String,Set<String>> typeClasspathDependencies() { return cpDependencies; }
115 
dependents()116     public Set<String> dependents() { return dependents; }
117 
118     @Override
equals(Object o)119     public boolean equals(Object o) {
120         return (o instanceof Package) && name.equals(((Package)o).name);
121     }
122 
123     @Override
hashCode()124     public int hashCode() {
125         return name.hashCode();
126     }
127 
128     @Override
compareTo(Package o)129     public int compareTo(Package o) {
130         return name.compareTo(o.name);
131     }
132 
addSource(Source s)133     public void addSource(Source s) {
134         sources.put(s.file().getPath(), s);
135     }
136 
137     private static Pattern DEP_PATTERN = Pattern.compile("(.*) -> (.*)");
parseAndAddDependency(String d, boolean cp)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 
addDependency(String fullyQualifiedFrom, String fullyQualifiedTo, boolean cp)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 
addDependent(String d)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      */
existsInJavacState()162     public boolean existsInJavacState() {
163         return artifacts.size() > 0 || !pubApi.isEmpty();
164     }
165 
hasPubApiChanged(PubApi newPubApi)166     public boolean hasPubApiChanged(PubApi newPubApi) {
167         return !newPubApi.isBackwardCompatibleWith(pubApi);
168     }
169 
setPubapi(PubApi newPubApi)170     public void setPubapi(PubApi newPubApi) {
171         pubApi = newPubApi;
172     }
173 
setDependencies(Map<String, Set<String>> ds, boolean cp)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 
save(StringBuilder b)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 
load(Module module, String l)189     static public Package load(Module module, String l) {
190         String name = l.substring(2);
191         return new Package(module, name);
192     }
193 
saveDependencies(StringBuilder b)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 
savePubapi(StringBuilder b)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 
savePackages(Map<String,Package> packages, StringBuilder b)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 
addArtifact(String a)230     public void addArtifact(String a) {
231         artifacts.put(a, new File(a));
232     }
233 
addArtifact(File f)234     public void addArtifact(File f) {
235         artifacts.put(f.getPath(), f);
236     }
237 
addArtifacts(Set<URI> as)238     public void addArtifacts(Set<URI> as) {
239         for (URI u : as) {
240             addArtifact(new File(u));
241         }
242     }
243 
setArtifacts(Set<URI> as)244     public void setArtifacts(Set<URI> as) {
245         Assert.check(!artifacts.isEmpty());
246         artifacts = new HashMap<>();
247         addArtifacts(as);
248     }
249 
loadArtifact(String l)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 
saveArtifacts(StringBuilder b)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      */
deleteArtifacts()288     public void deleteArtifacts() {
289         for (File a : artifacts.values()) {
290             a.delete();
291         }
292     }
293 }
294