/*
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.sjavac.pubapi.PubApi;
The Package class maintains meta information about a package.
For example its sources, dependents,its pubapi and its artifacts.
It might look odd that we track dependents/pubapi/artifacts on
a package level, but it makes sense since recompiling a full package
takes as long as recompiling a single java file in that package,
if you take into account the startup time of the jvm.
Also the dependency information will be much smaller (good for the javac_state file size)
and it simplifies tracking artifact generation, you do not always know from which
source a class file was generated, but you always know which package it belongs to.
It is also educational to see package dependencies triggering recompilation of
other packages. Even though the recompilation was perhaps not necessary,
the visible recompilation of the dependent packages indicates how much circular
dependencies your code has.
This is NOT part of any supported API.
If you write code that depends on this, you do so at your own risk.
This code and its internal interfaces are subject to change or
deletion without notice.
/**
* The Package class maintains meta information about a package.
* For example its sources, dependents,its pubapi and its artifacts.
*
* It might look odd that we track dependents/pubapi/artifacts on
* a package level, but it makes sense since recompiling a full package
* takes as long as recompiling a single java file in that package,
* if you take into account the startup time of the jvm.
*
* Also the dependency information will be much smaller (good for the javac_state file size)
* and it simplifies tracking artifact generation, you do not always know from which
* source a class file was generated, but you always know which package it belongs to.
*
* It is also educational to see package dependencies triggering recompilation of
* other packages. Even though the recompilation was perhaps not necessary,
* the visible recompilation of the dependent packages indicates how much circular
* dependencies your code has.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class Package implements Comparable<Package> {
// The module this package belongs to. (There is a legacy module with an empty string name,
// used for all legacy sources.)
private Module mod;
// Name of this package, module:pkg
// ex1 jdk.base:java.lang
// ex2 :java.lang (when in legacy mode)
private String name;
// The directory path to the package. If the package belongs to a module,
// then that module's file system name is part of the path.
private String dirname;
// This package has the following dependents, that depend on this package.
private Set<String> dependents = new HashSet<>();
// Fully qualified name of class in this package -> fully qualified name of dependency
private Map<String, Set<String>> dependencies = new TreeMap<>();
// Fully qualified name of class in this package -> fully qualified name of dependency on class path
private Map<String, Set<String>> cpDependencies = new TreeMap<>();
// This is the public api of this package.
private PubApi pubApi = new PubApi();
// Map from source file name to Source info object.
private Map<String,Source> sources = new HashMap<>();
// This package generated these artifacts.
private Map<String,File> artifacts = new HashMap<>();
public Package(Module m, String n) {
int c = n.indexOf(":");
Assert.check(c != -1);
Assert.check(m.name().equals(m.name()));
name = n;
dirname = n.replace('.', File.separatorChar);
if (m.name().length() > 0) {
// There is a module here, prefix the module dir name to the path.
dirname = m.dirname()+File.separatorChar+dirname;
}
}
public Module mod() { return mod; }
public String name() { return name; }
public String dirname() { return dirname; }
public Map<String,Source> sources() { return sources; }
public Map<String,File> artifacts() { return artifacts; }
public PubApi getPubApi() { return pubApi; }
public Map<String,Set<String>> typeDependencies() { return dependencies; }
public Map<String,Set<String>> typeClasspathDependencies() { return cpDependencies; }
public Set<String> dependents() { return dependents; }
@Override
public boolean equals(Object o) {
return (o instanceof Package) && name.equals(((Package)o).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(Package o) {
return name.compareTo(o.name);
}
public void addSource(Source s) {
sources.put(s.file().getPath(), s);
}
private static Pattern DEP_PATTERN = Pattern.compile("(.*) -> (.*)");
public void parseAndAddDependency(String d, boolean cp) {
Matcher m = DEP_PATTERN.matcher(d);
if (!m.matches())
throw new IllegalArgumentException("Bad dependency string: " + d);
addDependency(m.group(1), m.group(2), cp);
}
public void addDependency(String fullyQualifiedFrom,
String fullyQualifiedTo,
boolean cp) {
Map<String, Set<String>> map = cp ? cpDependencies : dependencies;
if (!map.containsKey(fullyQualifiedFrom))
map.put(fullyQualifiedFrom, new HashSet<>());
map.get(fullyQualifiedFrom).add(fullyQualifiedTo);
}
public void addDependent(String d) {
dependents.add(d);
}
Check if we have knowledge in the javac state that
describe the results of compiling this package before.
/**
* Check if we have knowledge in the javac state that
* describe the results of compiling this package before.
*/
public boolean existsInJavacState() {
return artifacts.size() > 0 || !pubApi.isEmpty();
}
public boolean hasPubApiChanged(PubApi newPubApi) {
return !newPubApi.isBackwardCompatibleWith(pubApi);
}
public void setPubapi(PubApi newPubApi) {
pubApi = newPubApi;
}
public void setDependencies(Map<String, Set<String>> ds, boolean cp) {
(cp ? cpDependencies : dependencies).clear();
for (String fullyQualifiedFrom : ds.keySet())
for (String fullyQualifiedTo : ds.get(fullyQualifiedFrom))
addDependency(fullyQualifiedFrom, fullyQualifiedTo, cp);
}
public void save(StringBuilder b) {
b.append("P ").append(name).append("\n");
Source.saveSources(sources, b);
saveDependencies(b);
savePubapi(b);
saveArtifacts(b);
}
static public Package load(Module module, String l) {
String name = l.substring(2);
return new Package(module, name);
}
public void saveDependencies(StringBuilder b) {
// Dependencies where *to* is among sources
for (String fullyQualifiedFrom : dependencies.keySet()) {
for (String fullyQualifiedTo : dependencies.get(fullyQualifiedFrom)) {
b.append(String.format("D S %s -> %s%n", fullyQualifiedFrom, fullyQualifiedTo));
}
}
// Dependencies where *to* is on class path
for (String fullyQualifiedFrom : cpDependencies.keySet()) {
for (String fullyQualifiedTo : cpDependencies.get(fullyQualifiedFrom)) {
b.append(String.format("D C %s -> %s%n", fullyQualifiedFrom, fullyQualifiedTo));
}
}
}
public void savePubapi(StringBuilder b) {
pubApi.asListOfStrings()
.stream()
.flatMap(l -> Stream.of("I ", l, "\n"))
.forEach(b::append);
}
public static void savePackages(Map<String,Package> packages, StringBuilder b) {
List<String> sorted_packages = new ArrayList<>();
for (String key : packages.keySet() ) {
sorted_packages.add(key);
}
Collections.sort(sorted_packages);
for (String s : sorted_packages) {
Package p = packages.get(s);
p.save(b);
}
}
public void addArtifact(String a) {
artifacts.put(a, new File(a));
}
public void addArtifact(File f) {
artifacts.put(f.getPath(), f);
}
public void addArtifacts(Set<URI> as) {
for (URI u : as) {
addArtifact(new File(u));
}
}
public void setArtifacts(Set<URI> as) {
Assert.check(!artifacts.isEmpty());
artifacts = new HashMap<>();
addArtifacts(as);
}
public void loadArtifact(String l) {
// Find next space after "A ".
int dp = l.indexOf(' ',2);
String fn = l.substring(2,dp);
long last_modified = Long.parseLong(l.substring(dp+1));
File f = new File(fn);
if (f.exists() && f.lastModified() != last_modified) {
// Hmm, the artifact on disk does not have the same last modified
// timestamp as the information from the build database.
// We no longer trust the artifact on disk. Delete it.
// The smart javac wrapper will then rebuild the artifact.
Log.debug("Removing "+f.getPath()+" since its timestamp does not match javac_state.");
f.delete();
}
artifacts.put(f.getPath(), f);
}
public void saveArtifacts(StringBuilder b) {
List<File> sorted_artifacts = new ArrayList<>();
for (File f : artifacts.values()) {
sorted_artifacts.add(f);
}
Collections.sort(sorted_artifacts);
for (File f : sorted_artifacts) {
// The last modified information is only used
// to detect tampering with the output dir.
// If the outputdir has been modified, not by javac,
// then a mismatch will be detected in the last modified
// timestamps stored in the build database compared
// to the timestamps on disk and the artifact will be deleted.
b.append("A "+f.getPath()+" "+f.lastModified()+"\n");
}
}
Always clean out a tainted package before it is recompiled.
/**
* Always clean out a tainted package before it is recompiled.
*/
public void deleteArtifacts() {
for (File a : artifacts.values()) {
a.delete();
}
}
}