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
26package com.sun.tools.sjavac;
27
28import java.io.File;
29import java.net.URI;
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Map;
36import java.util.Set;
37import java.util.TreeMap;
38import java.util.regex.Matcher;
39import java.util.regex.Pattern;
40import java.util.stream.Stream;
41
42import com.sun.tools.javac.util.Assert;
43import 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 */
68public 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}
294