# HG changeset patch # User alundblad # Date 1423776421 -3600 # Node ID 0332df0d383b2a8b869e0bc5c14b4251f5b06df7 # Parent b93a15ac3bdeb775c09c01473b4f0a4bd6306673 imported patch my-classpath-deps-00 diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java @@ -53,7 +53,6 @@ import javax.tools.DiagnosticListener; import javax.tools.FileObject; import javax.tools.JavaFileManager; -import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.StandardJavaFileManager; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java @@ -30,12 +30,11 @@ import java.net.URI; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; -import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.ProviderNotFoundException; -import java.nio.file.spi.FileSystemProvider; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -50,6 +49,7 @@ import com.sun.tools.javac.file.RelativePath.RelativeDirectory; import com.sun.tools.javac.nio.PathFileObject; import com.sun.tools.javac.util.Context; +import com.sun.tools.sjavac.comp.JavaFileObjectWithLocation; /** * A package-oriented index into the jrt: filesystem. @@ -230,6 +230,9 @@ } public boolean isInJRT(FileObject fo) { + if (fo instanceof JavaFileObjectWithLocation) { + fo = ((JavaFileObjectWithLocation) fo).getDelegate(); + } if (fo instanceof PathFileObject) { Path path = ((PathFileObject) fo).getPath(); return (path.getFileSystem() == jrtfs); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -25,25 +25,141 @@ package com.sun.tools.javac.tree; +import static com.sun.tools.javac.tree.JCTree.Tag.ANNOTATED_TYPE; +import static com.sun.tools.javac.tree.JCTree.Tag.APPLY; +import static com.sun.tools.javac.tree.JCTree.Tag.ASSERT; +import static com.sun.tools.javac.tree.JCTree.Tag.ASSIGN; +import static com.sun.tools.javac.tree.JCTree.Tag.BLOCK; +import static com.sun.tools.javac.tree.JCTree.Tag.BREAK; +import static com.sun.tools.javac.tree.JCTree.Tag.CASE; +import static com.sun.tools.javac.tree.JCTree.Tag.CATCH; +import static com.sun.tools.javac.tree.JCTree.Tag.CLASSDEF; +import static com.sun.tools.javac.tree.JCTree.Tag.CONDEXPR; +import static com.sun.tools.javac.tree.JCTree.Tag.CONTINUE; +import static com.sun.tools.javac.tree.JCTree.Tag.DOLOOP; +import static com.sun.tools.javac.tree.JCTree.Tag.ERRONEOUS; +import static com.sun.tools.javac.tree.JCTree.Tag.EXEC; +import static com.sun.tools.javac.tree.JCTree.Tag.FOREACHLOOP; +import static com.sun.tools.javac.tree.JCTree.Tag.FORLOOP; +import static com.sun.tools.javac.tree.JCTree.Tag.IDENT; +import static com.sun.tools.javac.tree.JCTree.Tag.IF; +import static com.sun.tools.javac.tree.JCTree.Tag.IMPORT; +import static com.sun.tools.javac.tree.JCTree.Tag.INDEXED; +import static com.sun.tools.javac.tree.JCTree.Tag.LABELLED; +import static com.sun.tools.javac.tree.JCTree.Tag.LAMBDA; +import static com.sun.tools.javac.tree.JCTree.Tag.LETEXPR; +import static com.sun.tools.javac.tree.JCTree.Tag.LITERAL; +import static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF; +import static com.sun.tools.javac.tree.JCTree.Tag.MODIFIERS; +import static com.sun.tools.javac.tree.JCTree.Tag.NEWARRAY; +import static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS; +import static com.sun.tools.javac.tree.JCTree.Tag.PACKAGEDEF; +import static com.sun.tools.javac.tree.JCTree.Tag.PARENS; +import static com.sun.tools.javac.tree.JCTree.Tag.REFERENCE; +import static com.sun.tools.javac.tree.JCTree.Tag.RETURN; +import static com.sun.tools.javac.tree.JCTree.Tag.SELECT; +import static com.sun.tools.javac.tree.JCTree.Tag.SKIP; +import static com.sun.tools.javac.tree.JCTree.Tag.SWITCH; +import static com.sun.tools.javac.tree.JCTree.Tag.SYNCHRONIZED; +import static com.sun.tools.javac.tree.JCTree.Tag.THROW; +import static com.sun.tools.javac.tree.JCTree.Tag.TOPLEVEL; +import static com.sun.tools.javac.tree.JCTree.Tag.TRY; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEARRAY; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEBOUNDKIND; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPECAST; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEIDENT; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEINTERSECTION; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEPARAMETER; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPETEST; +import static com.sun.tools.javac.tree.JCTree.Tag.TYPEUNION; +import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF; +import static com.sun.tools.javac.tree.JCTree.Tag.WHILELOOP; + import java.io.IOException; import java.io.StringWriter; -import java.util.*; +import java.util.Set; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeKind; import javax.tools.JavaFileObject; -import com.sun.source.tree.*; -import com.sun.source.tree.LambdaExpressionTree.BodyKind; -import com.sun.source.tree.MemberReferenceTree.ReferenceMode; -import com.sun.tools.javac.code.*; -import com.sun.tools.javac.code.Scope.*; -import com.sun.tools.javac.code.Symbol.*; -import com.sun.tools.javac.util.*; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.ArrayTypeTree; +import com.sun.source.tree.AssertTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.BreakTree; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ContinueTree; +import com.sun.source.tree.DoWhileLoopTree; +import com.sun.source.tree.EmptyStatementTree; +import com.sun.source.tree.EnhancedForLoopTree; +import com.sun.source.tree.ExpressionStatementTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.ForLoopTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.IfTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.IntersectionTypeTree; +import com.sun.source.tree.LabeledStatementTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.PackageTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.ReturnTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.SwitchTree; +import com.sun.source.tree.SynchronizedTree; +import com.sun.source.tree.ThrowTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TreeVisitor; +import com.sun.source.tree.TryTree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.TypeParameterTree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.UnionTypeTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.tree.WhileLoopTree; +import com.sun.source.tree.WildcardTree; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Scope.NamedImportScope; +import com.sun.tools.javac.code.Scope.StarImportScope; +import com.sun.tools.javac.code.Scope.WriteableScope; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; -import static com.sun.tools.javac.tree.JCTree.Tag.*; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Position; /** * Root class for abstract syntax tree nodes. It provides definitions diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/BuildState.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/BuildState.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/BuildState.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/BuildState.java @@ -26,12 +26,13 @@ package com.sun.tools.sjavac; import java.io.File; +import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; import com.sun.tools.javac.util.Assert; +import com.sun.tools.sjavac.pubapi.PubApi; /** * The build state class captures the source code and generated artifacts @@ -77,7 +78,7 @@ */ Module findModuleFromPackageName(String pkg) { int cp = pkg.indexOf(':'); - Assert.check(cp != -1); + Assert.check(cp != -1, "Could not find package name"); String mod = pkg.substring(0, cp); return lookupModule(mod); } @@ -154,21 +155,28 @@ */ public void calculateDependents() { dependents = new HashMap<>(); + for (String s : packages.keySet()) { Package p = packages.get(s); - for (String d : p.dependencies()) { - Set ss = dependents.get(d); - if (ss == null) { - ss = new HashSet<>(); - dependents.put(d, ss); - } + + // Collect all dependencies of the classes in this package + Set deps = p.typeDependencies() // maps fqName -> set of dependencies + .values() + .stream() + .reduce(Collections.emptySet(), Util::union); + + // Now reverse the direction + + for (String dep : deps) { // Add the dependent information to the global dependent map. - ss.add(s); - Package dp = packages.get(d); + String depPkgStr = ":" + dep.substring(0, dep.lastIndexOf('.')); + dependents.merge(depPkgStr, Collections.singleton(s), Util::union); + // Also add the dependent information to the package specific map. // Normally, you do not compile java.lang et al. Therefore // there are several packages that p depends upon that you // do not have in your state database. This is perfectly fine. + Package dp = packages.get(depPkgStr); if (dp != null) { // But this package did exist in the state database. dp.addDependent(p.name()); @@ -270,11 +278,21 @@ public void copyPackagesExcept(BuildState prev, Set recompiled, Set removed) { for (String pkg : prev.packages().keySet()) { // Do not copy recompiled or removed packages. - if (recompiled.contains(pkg) || removed.contains(pkg)) continue; + if (recompiled.contains(pkg) || removed.contains(pkg)) + continue; + Module mnew = findModuleFromPackageName(pkg); Package pprev = prev.packages().get(pkg); + + // Even though we haven't recompiled this package, we may have + // information about its public API: It may be a classpath dependency + if (packages.containsKey(pkg)) { + pprev.setPubapi(PubApi.mergeTypes(pprev.getPubApi(), + packages.get(pkg).getPubApi())); + } + mnew.addPackage(pprev); - // Do not forget to update the flattened data. + // Do not forget to update the flattened data. (See JDK-8071904) packages.put(pkg, pprev); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CleanProperties.java @@ -25,17 +25,25 @@ package com.sun.tools.sjavac; -import java.io.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.Writer; import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.HashSet; import java.util.Map; import java.util.Properties; +import java.util.Set; import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.Sjavac; /** @@ -63,8 +71,10 @@ Map> oldPackageDependencies, URI destRoot, Map> packageArtifacts, - Map> packageDependencies, - Map packagePublicApis, + Map>> packageDependencies, + Map>> packageCpDependencies, + Map packagePublicApis, + Map dependencyPublicApis, int debugLevel, boolean incremental, int numCores, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java @@ -30,10 +30,12 @@ import java.net.URI; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Set; -import java.util.Map; import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.CompilationResult; import com.sun.tools.sjavac.server.Sjavac; import com.sun.tools.sjavac.server.SysInfo; @@ -73,21 +75,25 @@ Map> oldPackageDependents, URI destRoot, final Map> packageArtifacts, - final Map> packageDependencies, - final Map packagePubapis, + final Map>> packageDependencies, + final Map>> packageCpDependencies, + final Map packagePubapis, + final Map dependencyPubapis, int debugLevel, boolean incremental, int numCores, final PrintStream out, - final PrintStream err) - { + final PrintStream err) { + + Log.debug("Performing CompileJavaPackages transform..."); + boolean rc = true; boolean concurrentCompiles = true; // Fetch the id. final String id = Util.extractStringOption("id", sjavac.serverSettings()); // Only keep portfile and sjavac settings.. - String psServerSettings = Util.cleanSubOptions(Util.set("portfile","sjavac","background","keepalive"), sjavac.serverSettings()); + //String psServerSettings = Util.cleanSubOptions(Util.set("portfile","sjavac","background","keepalive"), sjavac.serverSettings()); // Get maximum heap size from the server! SysInfo sysinfo = sjavac.getSysInfo(); @@ -210,20 +216,44 @@ final CompileChunk cc = compileChunks[i]; // Pass the num_cores and the id (appended with the chunk number) to the server. - final String cleanedServerSettings = psServerSettings+",poolsize="+numCores+",id="+id+"-"+i; - + Object lock = new Object(); requests[i] = new Thread() { @Override public void run() { rn[ii] = sjavac.compile("n/a", - id + "-" + ii, - args.prepJavacArgs(), - Collections.emptyList(), - cc.srcs, - visibleSources); - packageArtifacts.putAll(rn[ii].packageArtifacts); - packageDependencies.putAll(rn[ii].packageDependencies); - packagePubapis.putAll(rn[ii].packagePubapis); + id + "-" + ii, + args.prepJavacArgs(), + Collections.emptyList(), + cc.srcs, + visibleSources); + // In the code below we have to keep in mind that two + // different compilation results may include results for + // the same package. + synchronized (lock) { + + for (String pkg : rn[ii].packageArtifacts.keySet()) { + Set pkgArtifacts = rn[ii].packageArtifacts.get(pkg); + packageArtifacts.merge(pkg, pkgArtifacts, Util::union); + } + + for (String pkg : rn[ii].packageDependencies.keySet()) { + packageDependencies.putIfAbsent(pkg, new HashMap<>()); + packageDependencies.get(pkg).putAll(rn[ii].packageDependencies.get(pkg)); + } + + for (String pkg : rn[ii].packageCpDependencies.keySet()) { + packageCpDependencies.putIfAbsent(pkg, new HashMap<>()); + packageCpDependencies.get(pkg).putAll(rn[ii].packageCpDependencies.get(pkg)); + } + + for (String pkg : rn[ii].packagePubapis.keySet()) { + packagePubapis.merge(pkg, rn[ii].packagePubapis.get(pkg), PubApi::mergeTypes); + } + + for (String pkg : rn[ii].dependencyPubapis.keySet()) { + dependencyPubapis.merge(pkg, rn[ii].dependencyPubapis.get(pkg), PubApi::mergeTypes); + } + } } }; @@ -278,7 +308,6 @@ return rc; } - /** * Split up the sources into compile chunks. If old package dependents information * is available, sort the order of the chunks into the most dependent first! @@ -294,9 +323,9 @@ * @return */ CompileChunk[] createCompileChunks(Map> pkgSrcs, - Map> oldPackageDependents, - int numCompiles, - int sourcesPerCompile) { + Map> oldPackageDependents, + int numCompiles, + int sourcesPerCompile) { CompileChunk[] compileChunks = new CompileChunk[numCompiles]; for (int i=0; i> oldPackageDependents, URI destRoot, Map> packageArtifacts, - Map> packageDependencies, - Map packagePublicApis, + Map>> packageDependencies, + Map>> packageCpDependencies, + Map packagePublicApis, + Map dependencyPublicApis, int debugLevel, boolean incremental, int numCores, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/CopyFile.java @@ -25,13 +25,20 @@ package com.sun.tools.sjavac; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; import java.net.URI; -import java.util.Set; import java.util.HashSet; import java.util.Map; +import java.util.Set; import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.Sjavac; /** @@ -58,8 +65,10 @@ Map> oldPackageDependents, URI destRoot, Map> packageArtifacts, - Map> packageDependencies, - Map packagePubapis, + Map>> packageDependencies, + Map>> packageCpDependencies, + Map packagePubapis, + Map dependencyPubapis, int debugLevel, boolean incremental, int numCores, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java @@ -25,20 +25,27 @@ package com.sun.tools.sjavac; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; import java.net.URI; import java.nio.file.NoSuchFileException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.Sjavac; /** @@ -268,24 +275,25 @@ * Save the javac_state file. */ public void save() throws IOException { - if (!needsSaving) return; + if (!needsSaving) + return; try (FileWriter out = new FileWriter(javacState)) { StringBuilder b = new StringBuilder(); long millisNow = System.currentTimeMillis(); Date d = new Date(millisNow); - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); b.append("# This format might change at any time. Please do not depend on it.\n"); + b.append("# R arguments\n"); b.append("# M module\n"); b.append("# P package\n"); b.append("# S C source_tobe_compiled timestamp\n"); b.append("# S L link_only_source timestamp\n"); b.append("# G C generated_source timestamp\n"); b.append("# A artifact timestamp\n"); - b.append("# D dependency\n"); + b.append("# D S dependant -> source dependency\n"); + b.append("# D C dependant -> classpath dependency\n"); b.append("# I pubapi\n"); - b.append("# R arguments\n"); b.append("R ").append(theArgs).append("\n"); // Copy over the javac_state for the packages that did not need recompilation. @@ -312,6 +320,8 @@ boolean newCommandLine = false; boolean syntaxError = false; + Log.debug("Loading javac state file: " + db.javacState); + try (BufferedReader in = new BufferedReader(new FileReader(db.javacState))) { for (;;) { String l = in.readLine(); @@ -327,11 +337,14 @@ } else if (c == 'D') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } - lastPackage.loadDependency(l); + char depType = l.charAt(2); + if (depType != 'S' && depType != 'C') + throw new RuntimeException("Bad dependency string: " + l); + lastPackage.parseAndAddDependency(l.substring(4), depType == 'C'); } else if (c == 'I') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } - lastPackage.loadPubapi(l); + lastPackage.getPubApi().appendItem(l.substring(2)); // Strip "I " } else if (c == 'A') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } @@ -488,11 +501,90 @@ * Propagate recompilation through the dependency chains. * Avoid re-tainting packages that have already been compiled. */ - public void taintPackagesDependingOnChangedPackages(Set pkgs, Set recentlyCompiled) { + public void taintPackagesDependingOnChangedPackages(Set pkgsWithChangedPubApi, Set recentlyCompiled) { + // For each to-be-recompiled-candidates... + for (Package pkg : new HashSet<>(prev.packages().values())) { + // Find out what it depends upon... + Set deps = pkg.typeDependencies() + .values() + .stream() + .flatMap(s -> s.stream()) + .collect(Collectors.toSet()); + for (String dep : deps) { + String depPkg = ":" + dep.substring(0, dep.lastIndexOf('.')); + if (depPkg.equals(pkg.name())) + continue; + // Checking if that dependency has changed + if (pkgsWithChangedPubApi.contains(depPkg) && !recentlyCompiled.contains(pkg.name())) { + taintPackage(pkg.name(), "its depending on " + depPkg); + } + } + } + } + + /** + * Compare the javac_state recorded public apis of packages on the classpath + * with the actual public apis on the classpath. + */ + public void taintPackagesDependingOnChangedClasspathPackages() { + + // 1. Collect fully qualified names of all interesting classpath dependencies + Set fqDependencies = new HashSet<>(); for (Package pkg : prev.packages().values()) { - for (String dep : pkg.dependencies()) { - if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) { - taintPackage(pkg.name(), " its depending on "+dep); + // Check if this package was compiled. If it's presence is recorded + // because it was on the class path and we needed to save it's + // public api, it's not a candidate for tainting. + if (pkg.sources().isEmpty()) + continue; + + pkg.typeClasspathDependencies().values().forEach(fqDependencies::addAll); + } + + // 2. Extract the public APIs from the on disk .class files + // (Reason for doing step 1 in a separate phase is to avoid extracting + // public APIs of the same class twice.) + PubApiExtractor pubApiExtractor = new PubApiExtractor(options); + Map onDiskPubApi = new HashMap<>(); + for (String cpDep : fqDependencies) + onDiskPubApi.put(cpDep, pubApiExtractor.getPubApi(cpDep)); + + // 3. Compare them with the public APIs as of last compilation (loaded from javac_state) + nextPkg: + for (Package pkg : prev.packages().values()) { + // Check if this package was compiled. If it's presence is recorded + // because it was on the class path and we needed to save it's + // public api, it's not a candidate for tainting. + if (pkg.sources().isEmpty()) + continue; + + Set depsOfThisPkg = pkg.typeClasspathDependencies() + .values() + .stream() + .reduce(new HashSet<>(), Util::union); + for (String fqDep : depsOfThisPkg) { + + String depPkg = ":" + fqDep.substring(0, fqDep.lastIndexOf('.')); + PubApi prevPkgApi = prev.packages().get(depPkg).getPubApi(); + + // This PubApi directly lists the members of the class, i.e. [ MEMBER1, MEMBER2, ... ] + PubApi prevDepApi = prevPkgApi.types.get(fqDep).pubApi; + + // In order to dive *into* the class, we need to add + // .types.get(fqDep).pubApi below. + PubApi currentDepApi = onDiskPubApi.get(fqDep).types.get(fqDep).pubApi; + + if (!currentDepApi.isBackwardCompatibleWith(prevDepApi)) { + String apiDiff = currentDepApi.diff(prevDepApi); + taintPackage(pkg.name(), "depends on classpath " + + "package which has an updated package api (" + + apiDiff + ")"); + //Log.debug("========================================"); + //Log.debug("------ PREV API ------------------------"); + //prevDepApi.asListOfStrings().forEach(Log::debug); + //Log.debug("------ CURRENT API ---------------------"); + //currentDepApi.asListOfStrings().forEach(Log::debug); + //Log.debug("========================================"); + continue nextPkg; } } } @@ -660,7 +752,6 @@ Map suffixRules = new HashMap<>(); suffixRules.put(".java", compileJavaPackages); compileJavaPackages.setExtra(args); - rcValue[0] = perform(sjavac, binDir, suffixRules); recentlyCompiled.addAll(taintedPackages()); clearTaintedPackages(); @@ -668,6 +759,11 @@ taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); packagesWithChangedPublicApis = new HashSet<>(); return again && rcValue[0]; + + // TODO: Figure out why 'again' checks packagesWithChangedPublicAPis. + // (It shouldn't matter if packages had changed pub apis as long as no + // one depends on them. Wouldn't it make more sense to let 'again' + // depend on taintedPackages?) } /** @@ -699,68 +795,101 @@ Map>> groupedSources = new HashMap<>(); for (Source src : now.sources().values()) { Transformer t = suffixRules.get(src.suffix()); - if (t != null) { + if (t != null) { if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) { addFileToTransform(groupedSources, t, src); } } } // Go through the transforms and transform them. - for (Map.Entry>> e : groupedSources.entrySet()) { + for (Map.Entry>> e : groupedSources.entrySet()) { Transformer t = e.getKey(); - Map> srcs = e.getValue(); - // These maps need to be synchronized since multiple threads will be writing results into them. - Map> packageArtifacts = - Collections.synchronizedMap(new HashMap>()); - Map> packageDependencies = - Collections.synchronizedMap(new HashMap>()); - Map packagePublicApis = - Collections.synchronizedMap(new HashMap()); + Map> srcs = e.getValue(); + // These maps need to be synchronized since multiple threads will be + // writing results into them. + Map> packageArtifacts = Collections.synchronizedMap(new HashMap<>()); + Map>> packageDependencies = Collections.synchronizedMap(new HashMap<>()); + Map>> packageCpDependencies = Collections.synchronizedMap(new HashMap<>()); + Map packagePublicApis = Collections.synchronizedMap(new HashMap<>()); + Map dependencyPublicApis = Collections.synchronizedMap(new HashMap<>()); - boolean r = t.transform(sjavac, - srcs, - visibleSrcs, - visibleClasses, - prev.dependents(), - outputDir.toURI(), - packageArtifacts, - packageDependencies, - packagePublicApis, - 0, - isIncremental(), - numCores, - out, - err); - if (!r) rc = false; + boolean r = t.transform(sjavac, + srcs, + visibleSrcs, + visibleClasses, + prev.dependents(), + outputDir.toURI(), + packageArtifacts, + packageDependencies, + packageCpDependencies, + packagePublicApis, + dependencyPublicApis, + 0, + isIncremental(), + numCores, + out, + err); + if (!r) + rc = false; for (String p : srcs.keySet()) { recompiledPackages.add(p); } // The transform is done! Extract all the artifacts and store the info into the Package objects. - for (Map.Entry> a : packageArtifacts.entrySet()) { + for (Map.Entry> a : packageArtifacts.entrySet()) { Module mnow = now.findModuleFromPackageName(a.getKey()); mnow.addArtifacts(a.getKey(), a.getValue()); } // Extract all the dependencies and store the info into the Package objects. - for (Map.Entry> a : packageDependencies.entrySet()) { - Set deps = a.getValue(); + for (Map.Entry>> a : packageDependencies.entrySet()) { + Map> deps = a.getValue(); Module mnow = now.findModuleFromPackageName(a.getKey()); - mnow.setDependencies(a.getKey(), deps); + mnow.setDependencies(a.getKey(), deps, false); } - // Extract all the pubapis and store the info into the Package objects. - for (Map.Entry a : packagePublicApis.entrySet()) { - Module mprev = prev.findModuleFromPackageName(a.getKey()); - List pubapi = Package.pubapiToList(a.getValue()); + for (Map.Entry>> a : packageCpDependencies.entrySet()) { + Map> deps = a.getValue(); Module mnow = now.findModuleFromPackageName(a.getKey()); - mnow.setPubapi(a.getKey(), pubapi); - if (mprev.hasPubapiChanged(a.getKey(), pubapi)) { + mnow.setDependencies(a.getKey(), deps, true); + } + + // This map contains the public api of the types that this + // compilation depended upon. This means that it may not contain + // full packages. In other words, we shouldn't remove knowledge of + // public apis but merge these with what we already have. + for (Map.Entry a : dependencyPublicApis.entrySet()) { + String pkg = a.getKey(); + PubApi packagePartialPubApi = a.getValue(); + Package pkgNow = now.findModuleFromPackageName(pkg).lookupPackage(pkg); + PubApi currentPubApi = pkgNow.getPubApi(); + PubApi newPubApi = PubApi.mergeTypes(currentPubApi, packagePartialPubApi); + pkgNow.setPubapi(newPubApi); + + // See JDK-8071904 + if (now.packages().containsKey(pkg)) + now.packages().get(pkg).setPubapi(newPubApi); + else + now.packages().put(pkg, pkgNow); + } + + // The packagePublicApis cover entire packages (since sjavac compiles + // stuff on package level). This means that if a type is missing + // in the public api of a given package, it means that it has been + // removed. In other words, we should *set* the pubapi to whatever + // this map contains, and not merge it with what we already have. + for (Map.Entry a : packagePublicApis.entrySet()) { + String pkg = a.getKey(); + PubApi newPubApi = a.getValue(); + Module mprev = prev.findModuleFromPackageName(pkg); + Module mnow = now.findModuleFromPackageName(pkg); + mnow.setPubapi(pkg, newPubApi); + if (mprev.hasPubapiChanged(pkg, newPubApi)) { // Aha! The pubapi of this package has changed! // It can also be a new compile from scratch. - if (mprev.lookupPackage(a.getKey()).existsInJavacState()) { + if (mprev.lookupPackage(pkg).existsInJavacState()) { // This is an incremental compile! The pubapi // did change. Trigger recompilation of dependents. - packagesWithChangedPublicApis.add(a.getKey()); - Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!"); + packagesWithChangedPublicApis.add(pkg); + Log.info("The API of " + Util.justPackageName(pkg) + " has changed!"); } } } @@ -791,17 +920,21 @@ } /** - * Compare the calculate source list, with an explicit list, usually supplied from the makefile. - * Used to detect bugs where the makefile and sjavac have different opinions on which files - * should be compiled. + * Compare the calculate source list, with an explicit list, usually + * supplied from the makefile. Used to detect bugs where the makefile and + * sjavac have different opinions on which files should be compiled. */ - public void compareWithMakefileList(File makefileSourceList) throws ProblemException { - // If we are building on win32 using for example cygwin the paths in the makefile source list + public void compareWithMakefileList(File makefileSourceList) + throws ProblemException { + // If we are building on win32 using for example cygwin the paths in the + // makefile source list // might be /cygdrive/c/.... which does not match c:\.... - // We need to adjust our calculated sources to be identical, if necessary. + // We need to adjust our calculated sources to be identical, if + // necessary. boolean mightNeedRewriting = File.pathSeparatorChar == ';'; - if (makefileSourceList == null) return; + if (makefileSourceList == null) + return; Set calculatedSources = new HashSet<>(); Set listedSources = new HashSet<>(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Module.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Module.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Module.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Module.java @@ -28,10 +28,11 @@ import java.io.File; import java.net.URI; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; +import com.sun.tools.sjavac.pubapi.PubApi; + /** * The module is the root of a set of packages/sources/artifacts. * At the moment there is only one module in use, the empty/no-name/default module. @@ -86,8 +87,7 @@ return new Module(name, ""); } - public static void saveModules(Map ms, StringBuilder b) - { + public static void saveModules(Map ms, StringBuilder b) { for (Module m : ms.values()) { m.save(b); } @@ -98,6 +98,7 @@ } public Package lookupPackage(String pkg) { + // See JDK-8071904 Package p = packages.get(pkg); if (p == null) { p = new Package(this, pkg); @@ -124,18 +125,17 @@ } } - public void setDependencies(String pkg, Set deps) { - Package p = lookupPackage(pkg); - p.setDependencies(deps); + public void setDependencies(String pkg, Map> deps, boolean cp) { + lookupPackage(pkg).setDependencies(deps, cp); } - public void setPubapi(String pkg, List ps) { + public void setPubapi(String pkg, PubApi ps) { Package p = lookupPackage(pkg); p.setPubapi(ps); } - public boolean hasPubapiChanged(String pkg, List ps) { + public boolean hasPubapiChanged(String pkg, PubApi newPubApi) { Package p = lookupPackage(pkg); - return p.hasPubapiChanged(ps); + return p.hasPubApiChanged(newPubApi); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Package.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Package.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Package.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Package.java @@ -31,11 +31,16 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; 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. @@ -71,12 +76,16 @@ // 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 depends on these packages. - private Set dependencies = new HashSet<>(); // This package has the following dependents, that depend on this package. private Set dependents = new HashSet<>(); + + // Fully qualified name of class in this package -> fully qualified name of dependency + private Map> dependencies = new TreeMap<>(); + // Fully qualified name of class in this package -> fully qualified name of dependency on class path + private Map> cpDependencies = new TreeMap<>(); + // This is the public api of this package. - private List pubapi = new ArrayList<>(); + private PubApi pubApi = new PubApi(); // Map from source file name to Source info object. private Map sources = new HashMap<>(); // This package generated these artifacts. @@ -85,7 +94,6 @@ public Package(Module m, String n) { int c = n.indexOf(":"); Assert.check(c != -1); - String mn = n.substring(0,c); Assert.check(m.name().equals(m.name())); name = n; dirname = n.replace('.', File.separatorChar); @@ -100,9 +108,11 @@ public String dirname() { return dirname; } public Map sources() { return sources; } public Map artifacts() { return artifacts; } - public List pubapi() { return pubapi; } + public PubApi getPubApi() { return pubApi; } - public Set dependencies() { return dependencies; } + public Map> typeDependencies() { return dependencies; } + public Map> typeClasspathDependencies() { return cpDependencies; } + public Set dependents() { return dependents; } @Override @@ -124,70 +134,48 @@ sources.put(s.file().getPath(), s); } - public void addDependency(String d) { - dependencies.add(d); + 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> 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); } - public void addPubapi(String p) { - pubapi.add(p); - } - /** * 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.size() > 0; + return artifacts.size() > 0 || !pubApi.isEmpty(); } - public static List pubapiToList(String ps) - { - String[] lines = ps.split("\n"); - List r = new ArrayList<>(); - for (String l : lines) { - r.add(l); - } - return r; + public boolean hasPubApiChanged(PubApi newPubApi) { + return !newPubApi.isBackwardCompatibleWith(pubApi); } - public boolean hasPubapiChanged(List ps) { - Iterator i = ps.iterator(); - Iterator j = pubapi.iterator(); - int line = 0; - while (i.hasNext() && j.hasNext()) { - String is = i.next(); - String js = j.next(); - if (!is.equals(js)) { - Log.debug("Change in pubapi for package "+name+" line "+line); - Log.debug("Old: "+js); - Log.debug("New: "+is); - return true; - } - line++; - } - if ((i.hasNext() && !j.hasNext() ) || - (!i.hasNext() && j.hasNext())) { - Log.debug("Change in pubapi for package "+name); - if (i.hasNext()) { - Log.debug("New has more lines!"); - } else { - Log.debug("Old has more lines!"); - } - return true; - } - return false; + public void setPubapi(PubApi newPubApi) { + pubApi = newPubApi; } - public void setPubapi(List ps) { - pubapi = ps; - } - - public void setDependencies(Set ds) { - dependencies = ds; + public void setDependencies(Map> 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) { @@ -203,31 +191,28 @@ return new Package(module, name); } - public void loadDependency(String l) { - String n = l.substring(2); - addDependency(n); - } + public void saveDependencies(StringBuilder b) { - public void loadPubapi(String l) { - String pi = l.substring(2); - addPubapi(pi); - } + // 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)); + } + } - public void saveDependencies(StringBuilder b) { - List sorted_dependencies = new ArrayList<>(); - for (String key : dependencies) { - sorted_dependencies.add(key); - } - Collections.sort(sorted_dependencies); - for (String a : sorted_dependencies) { - b.append("D "+a+"\n"); + // 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) { - for (String l : pubapi) { - b.append("I "+l+"\n"); - } + pubApi.asListOfStrings() + .stream() + .flatMap(l -> Stream.of("I ", l, "\n")) + .forEach(b::append); } public static void savePackages(Map packages, StringBuilder b) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/PubApiExtractor.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/PubApiExtractor.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/PubApiExtractor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012-2014, 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.PrintWriter; +import java.util.Arrays; + +import javax.tools.JavaCompiler.CompilationTask; + +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.code.ClassFinder; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import com.sun.tools.sjavac.comp.PubapiVisitor; +import com.sun.tools.sjavac.comp.SmartFileManager; +import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; + + +public class PubApiExtractor { + // Setup a compiler context for finding classes in the classpath + // and to execute annotation processors. + Context context; + CompilationTask task; + + /** + * Setup a compilation context, used for reading public apis of classes on the classpath + * as well as annotation processors. + */ + public PubApiExtractor(Options options) { + JavacTool compiler = com.sun.tools.javac.api.JavacTool.create(); + SmartFileManager fileManager = new SmartFileManager(compiler.getStandardFileManager(null, null, null)); + context = new com.sun.tools.javac.util.Context(); + String[] args = options.prepJavacArgs(); + task = compiler.getTask(new PrintWriter(System.err), + fileManager, + null, + Arrays.asList(args), + null, + null, + context); + // Trigger a creation of the JavaCompiler, necessary to get a sourceCompleter for ClassFinder. + // The sourceCompleter is used for build situations where a classpath class references other classes + // that happens to be on the sourcepath. + JavaCompiler.instance(context); + +// context.put(JavaFileManager.class, fileManager); + } + + public PubApi getPubApi(String fullyQualifiedClassName) { + ClassFinder cr = ClassFinder.instance(context); + Names ns = Names.instance(context); + Name n = ns.fromString(fullyQualifiedClassName); + ClassSymbol cs = cr.loadClass(n); + PubapiVisitor v = new PubapiVisitor(); + v.visit(cs); + return v.getCollectedPubApi(); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/SjavacScratch.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/SjavacScratch.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/SjavacScratch.java @@ -0,0 +1,10 @@ +package com.sun.tools.sjavac; + +public class SjavacScratch { + + public static void main(String[] args) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Source.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Source.java @@ -373,8 +373,6 @@ return currentModule; } - private static boolean gurka = false; - static private void scanDirectory(File dir, int rootPrefix, File root, Set suffixes, List excludes, List includes, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Transformer.java @@ -27,10 +27,11 @@ import java.io.PrintStream; import java.net.URI; +import java.util.Map; import java.util.Set; -import java.util.Map; import com.sun.tools.sjavac.options.Options; +import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.Sjavac; /** @@ -89,8 +90,10 @@ Map> oldPackageDependencies, URI destRoot, Map> packageArtifacts, - Map> packageDependencies, - Map packagePublicApis, + Map>> packageDependencies, // Package name -> Fully Qualified Type [from] -> Set of fully qualified type [to] + Map>> packageCpDependencies, // Package name -> Fully Qualified Type [from] -> Set of fully qualified type [to] + Map packagePublicApis, + Map dependencyApis, int debugLevel, boolean incremental, int numCores, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/Util.java @@ -30,9 +30,13 @@ import java.io.StringWriter; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.StringTokenizer; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Utilities. @@ -183,6 +187,13 @@ return union; } + public static Set subtract(Set orig, + Set toSubtract) { + Set difference = new HashSet<>(orig); + difference.removeAll(toSubtract); + return difference; + } + public static String getStackTrace(Throwable t) { StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); @@ -193,4 +204,16 @@ public static File pathToFile(Path path) { return path == null ? null : path.toFile(); } + + public static Set intersection(Collection c1, + Collection c2) { + Set intersection = new HashSet(c1); + intersection.retainAll(c2); + return intersection; + } + + public static Map indexBy(Collection c, + Function indexFunction) { + return c.stream().collect(Collectors.toMap(indexFunction, o -> o)); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/client/ClientMain.java @@ -93,6 +93,11 @@ if (hdrdir != null && !createIfMissing(hdrdir)) return -1; + Log.debug("=========================================================="); + Log.debug("Launching sjavac client with the following parameters:"); + Log.debug(" " + options.getStateArgsString()); + Log.debug("=========================================================="); + // Load the prev build state database. JavacState javac_state = JavacState.load(options, out, err); @@ -167,6 +172,9 @@ javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to); javac_state.setVisibleSources(sources_to_link_to); + int round = 0; + printRound(round); + // If there is any change in the source files, taint packages // and mark the database in need of saving. javac_state.checkSourceStatus(false); @@ -188,6 +196,10 @@ // Go through all sources and taint all packages that miss artifacts. javac_state.taintPackagesThatMissArtifacts(); + // Check recorded classpath public apis. Taint packages that depend on + // classpath classes whose public apis have changed. + javac_state.taintPackagesDependingOnChangedClasspathPackages(); + // Now clean out all known artifacts belonging to tainted packages. javac_state.deleteClassArtifactsInTaintedPackages(); // Copy files, for example property files, images files, xml files etc etc. @@ -231,11 +243,22 @@ } do { + if (round > 0) + printRound(round); // Clean out artifacts in tainted packages. javac_state.deleteClassArtifactsInTaintedPackages(); again = javac_state.performJavaCompilations(sjavac, options, recently_compiled, rc); - if (!rc[0]) break; + if (!rc[0]) { + Log.debug("Compilation failed."); + break; + } + if (!again) { + Log.debug("Nothing left to do."); + } + round++; } while (again); + Log.debug("No need to do another round."); + // Only update the state if the compile went well. if (rc[0]) { javac_state.save(); @@ -323,4 +346,10 @@ } } + private static void printRound(int round) { + Log.debug("****************************************"); + Log.debug("* Round " + round + " *"); + Log.debug("****************************************"); + } + } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/Dependencies.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/Dependencies.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/Dependencies.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/Dependencies.java @@ -25,19 +25,25 @@ package com.sun.tools.sjavac.comp; -import javax.lang.model.element.Element; -import java.util.Arrays; -import java.util.Comparator; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; import com.sun.tools.javac.code.Symbol.ClassSymbol; -import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; -import com.sun.tools.javac.util.Name; +import com.sun.tools.sjavac.Util; +import com.sun.tools.sjavac.pubapi.PubApi; /** Utility class containing dependency information between packages * and the pubapi for a package. @@ -52,16 +58,17 @@ // The log to be used for error reporting. protected Log log; - // Map from package name to packages that the package depends upon. - protected Map> deps; - // This is the set of all packages that are supplied - // through the java files at the command line. - protected Set explicitPackages; - // Map from a package name to its public api. + // CompilationUnit -> Fully qualified type -> set of dependencies + protected Map>> deps = new HashMap<>(); + + // CompilationUnit -> Fully qualified type -> set of dependencies + protected Map>> cpDeps = new HashMap<>(); + + // Map from a class name to its public api. // Will the Name encode the module in the future? // If not, this will have to change to map from Module+Name to public api. - protected Map publicApiPerClass; + protected Map publicApiPerClass = new HashMap<>(); public static Dependencies instance(Context context) { Dependencies instance = context.get(dependenciesKey); @@ -73,109 +80,98 @@ private Dependencies(Context context) { context.put(dependenciesKey, this); log = Log.instance(context); - deps = new HashMap<>(); - explicitPackages = new HashSet<>(); - publicApiPerClass = new HashMap<>(); - } - - /** - * Fetch the set of dependencies that are relevant to the compile - * that has just been performed. I.e. we are only interested in - * dependencies for classes that were explicitly compiled. - * @return - */ - public Map> getDependencies() { - Map> new_deps = new HashMap<>(); - if (explicitPackages == null) return new_deps; - for (Name pkg : explicitPackages) { - Set set = deps.get(pkg); - if (set != null) { - Set new_set = new_deps.get(pkg.toString()); - if (new_set == null) { - new_set = new HashSet<>(); - // Modules beware.... - new_deps.put(":"+pkg.toString(), new_set); - } - for (Name d : set) { - new_set.add(":"+d.toString()); - } - } - } - return new_deps; - } - - static class CompareNames implements Comparator { - public int compare(Name a, Name b) { - return a.toString().compareTo(b.toString()); - } - } /** * Convert the map from class names to their pubapi to a map - * from package names to their pubapi (which is the sorted concatenation - * of all the class pubapis) + * from package names to their pubapi. */ - public Map getPubapis() { - Map publicApiPerPackage = new HashMap<>(); - if (publicApiPerClass == null) return publicApiPerPackage; - Name[] keys = publicApiPerClass.keySet().toArray(new Name[0]); - Arrays.sort(keys, new CompareNames()); - StringBuffer newPublicApi = new StringBuffer(); - int i=0; - String prevPkg = ""; - for (Name k : keys) { - String cn = k.toString(); - String pn = ""; - int dp = cn.lastIndexOf('.'); - if (dp != -1) { - pn = cn.substring(0,dp); - } - if (!pn.equals(prevPkg)) { - if (!prevPkg.equals("")) { - // Add default module name ":" - publicApiPerPackage.put(":"+prevPkg, newPublicApi.toString()); - } - newPublicApi = new StringBuffer(); - prevPkg = pn; - } - newPublicApi.append(publicApiPerClass.get(k)); - i++; + public Map getPubapis(Collection explicitJFOs, boolean explicits) { + + // Maps ":java.lang" to a package level pub api (with only types on top level) + Map result = new HashMap<>(); + for (ClassSymbol cs : publicApiPerClass.keySet()) { + + boolean amongExplicits = explicitJFOs.contains(cs.sourcefile); + if (explicits != amongExplicits) + continue; + + String pkg = ":" + cs.packge().fullname; + PubApi currentPubApi = result.getOrDefault(pkg, new PubApi()); + result.put(pkg, PubApi.mergeTypes(currentPubApi, publicApiPerClass.get(cs))); } - if (!prevPkg.equals("")) - publicApiPerPackage.put(":"+prevPkg, newPublicApi.toString()); - return publicApiPerPackage; + + return result; } /** - * Visit the api of a class and construct a pubapi string and + * Visit the api of a class and construct a pubapi and * store it into the pubapi_perclass map. */ + @SuppressWarnings("deprecation") public void visitPubapi(Element e) { - Name n = ((ClassSymbol)e).fullname; - Name p = ((ClassSymbol)e).packge().fullname; - StringBuffer sb = publicApiPerClass.get(n); - Assert.check(sb == null); - sb = new StringBuffer(); - PubapiVisitor v = new PubapiVisitor(sb); + + // Skip anonymous classes for now + if (e == null) + return; + + PubapiVisitor v = new PubapiVisitor(); v.visit(e); - if (sb.length()>0) { - publicApiPerClass.put(n, sb); + publicApiPerClass.put((ClassSymbol) e, v.getCollectedPubApi()); + } + + public void collect(Location loc, JCCompilationUnit cu, String from, TypeSymbol toSym) { + + Map>> depsMap; + + // TODO: Just because it's not CLASS_PATH doesn't mean it's to-be-compiled path + depsMap = loc == StandardLocation.CLASS_PATH ? cpDeps : deps; + + if (!depsMap.containsKey(cu)) + depsMap.put(cu, new HashMap<>()); + Map> map = depsMap.get(cu); + map.merge(from, Collections.singleton(toSym), Util::union); + } + + // Package -> Type [from] -> Set of Type [to] + public Map>> getDependencies(Collection explicits) { + return getTypeDependenciesHelper(explicits, deps); + } + public Map>> getCpDependencies(Collection explicits) { + return getTypeDependenciesHelper(explicits, cpDeps); + } + public Map>> getTypeDependenciesHelper(Collection explicits, + Map>> depsMap) { + Map>> result = new HashMap<>(); + for (JCCompilationUnit cu : depsMap.keySet()) { + + if (!explicits.contains(cu.sourcefile)) + continue; + + // Dependencies to add to package entry + Map> src = depsMap.getOrDefault(cu, Collections.emptyMap()); + + // Sjavac does currently not handle default packages. + if (cu.getPackage() == null) + continue; + + String key = ":" + cu.getPackage().packge.fullname.toString(); + + // Find (or create) destination + Map> dst; + if (result.containsKey(key)) + dst = result.get(key); + else + result.put(key, dst = new HashMap<>()); + + for (String fqFrom : src.keySet()) { + dst.put(fqFrom, src.get(fqFrom) + .stream() + .map(ts -> ts.type.tsym.flatName().toString()) + .collect(Collectors.toSet())); + } } - explicitPackages.add(p); - } + return result; + } - /** - * Collect a dependency. curr_pkg is marked as depending on dep_pkg. - */ - public void collect(Name currPkg, Name depPkg) { - if (!currPkg.equals(depPkg)) { - Set theset = deps.get(currPkg); - if (theset==null) { - theset = new HashSet<>(); - deps.put(currPkg, theset); - } - theset.add(depPkg); - } - } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/FileObjectWithLocation.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/FileObjectWithLocation.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/FileObjectWithLocation.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, 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.comp; + +import javax.tools.FileObject; +import javax.tools.ForwardingFileObject; +import javax.tools.JavaFileManager.Location; + +public class FileObjectWithLocation extends ForwardingFileObject { + + private final Location loc; + + public FileObjectWithLocation(F delegate, Location loc) { + super(delegate); + this.loc = loc; + } + + public Location getLocation() { + return loc; + } + + public FileObject getDelegate() { + return fileObject; + } + + public String toString() { + return "FileObjectWithLocation[" + fileObject + "]"; + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/JavaFileObjectWithLocation.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/JavaFileObjectWithLocation.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/JavaFileObjectWithLocation.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014, 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.comp; + +import javax.tools.ForwardingJavaFileObject; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.api.ClientCodeWrapper.Trusted; + +@Trusted +public class JavaFileObjectWithLocation extends ForwardingJavaFileObject { + + private final Location loc; + + public JavaFileObjectWithLocation(F delegate, Location loc) { + super(delegate); + this.loc = loc; + } + + public Location getLocation() { + return loc; + } + + public F getDelegate() { + return fileObject; + } + + public String toString() { + return "JavaFileObjectWithLocation[" + fileObject + "]"; + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java @@ -41,6 +41,7 @@ import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.Name; +import com.sun.tools.sjavac.Log; public class PathAndPackageVerifier implements TaskListener { @@ -50,30 +51,39 @@ @Override @DefinedBy(Api.COMPILER_TREE) - public void started(TaskEvent e) { + public void finished(TaskEvent e) { + if (e.getKind() == TaskEvent.Kind.ANALYZE) { + + + CompilationUnitTree cu = e.getCompilationUnit(); + if (cu == null) + return; + + JavaFileObject jfo = cu.getSourceFile(); + if (jfo == null) + return; // No source file -> package doesn't matter + + JCTree pkg = (JCTree) cu.getPackageName(); + if (pkg == null) + return; // Default package. See JDK-8048144. + + Path dir = Paths.get(jfo.toUri()).normalize().getParent(); + if (!checkPathAndPackage(dir, pkg)) + misplacedCompilationUnits.add(cu); + } + + if (e.getKind() == TaskEvent.Kind.COMPILATION) { + + for (CompilationUnitTree cu : misplacedCompilationUnits) { + Log.error("Misplaced compilation unit."); + Log.error(" Directory: " + Paths.get(cu.getSourceFile().toUri()).getParent()); + Log.error(" Package: " + cu.getPackageName()); + } + } } - - @Override - @DefinedBy(Api.COMPILER_TREE) - public void finished(TaskEvent e) { - if (e.getKind() != TaskEvent.Kind.ANALYZE) - return; - - CompilationUnitTree cu = e.getCompilationUnit(); - if (cu == null) - return; - - JavaFileObject jfo = cu.getSourceFile(); - if (jfo == null) - return; // No source file -> package doesn't matter - - JCTree pkg = (JCTree) cu.getPackageName(); - if (pkg == null) - return; // Default package. See JDK-8048144. - - Path dir = Paths.get(jfo.toUri()).normalize().getParent(); - if (!checkPathAndPackage(dir, pkg)) - misplacedCompilationUnits.add(cu); + + public boolean errorsDiscovered() { + return misplacedCompilationUnits.size() > 0; } /* Returns true if dir matches pkgName. @@ -94,10 +104,6 @@ return !pkgIter.hasNext(); /*&& !pathIter.hasNext() See JDK-8059598 */ } - public Set getMisplacedCompilationUnits() { - return misplacedCompilationUnits; - } - /* Iterates over the names of the parents of the given path: * Example: dir1/dir2/dir3 results in dir3 -> dir2 -> dir1 */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PooledSjavac.java @@ -29,12 +29,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import com.sun.tools.sjavac.Log; import com.sun.tools.sjavac.server.CompilationResult; @@ -58,28 +55,13 @@ public PooledSjavac(Sjavac delegate, int poolsize) { Objects.requireNonNull(delegate); this.delegate = delegate; - pool = Executors.newFixedThreadPool(poolsize, new ThreadFactory() { - AtomicInteger count = new AtomicInteger(); - @Override - public Thread newThread(Runnable runnable) { - String cls = PooledSjavac.class.getSimpleName(); - int num = count.incrementAndGet(); - Thread t = new Thread(runnable, cls + "-" + num); - t.setDaemon(true); - return t; - } - }); + pool = Executors.newFixedThreadPool(poolsize); } @Override public SysInfo getSysInfo() { try { - return pool.submit(new Callable() { - @Override - public SysInfo call() throws Exception { - return delegate.getSysInfo(); - } - }).get(); + return pool.submit(() -> delegate.getSysInfo()).get(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Error during getSysInfo", e); @@ -94,16 +76,13 @@ final Set sourcesToCompile, final Set visibleSources) { try { - return pool.submit(new Callable() { - @Override - public CompilationResult call() throws Exception { - return delegate.compile(protocolId, - invocationId, - args, - explicitSources, - sourcesToCompile, - visibleSources); - } + return pool.submit(() -> { + return delegate.compile(protocolId, + invocationId, + args, + explicitSources, + sourcesToCompile, + visibleSources); }).get(); } catch (Exception e) { e.printStackTrace(); @@ -113,6 +92,7 @@ @Override public void shutdown() { + Log.debug("Shutting down PooledSjavac"); pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate @@ -122,8 +102,6 @@ if (!pool.awaitTermination(60, TimeUnit.SECONDS)) Log.error("ThreadPool did not terminate"); } - // Grace period for thread termination - Thread.sleep(1000); } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java @@ -25,17 +25,27 @@ package com.sun.tools.sjavac.comp; -import java.util.Iterator; +import static javax.lang.model.element.Modifier.PRIVATE; + import java.util.List; -import javax.lang.model.element.Modifier; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementScanner9; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; +import com.sun.tools.sjavac.pubapi.PubApi; +import com.sun.tools.sjavac.pubapi.PubApiTypeParam; +import com.sun.tools.sjavac.pubapi.PubMethod; +import com.sun.tools.sjavac.pubapi.PubType; +import com.sun.tools.sjavac.pubapi.PubVar; +import com.sun.tools.sjavac.pubapi.TypeDesc; /** Utility class that constructs a textual representation * of the public api of a class. @@ -47,40 +57,59 @@ */ public class PubapiVisitor extends ElementScanner9 { - StringBuffer sb; - // Important that it is 1! Part of protocol over wire, silly yes. - // Fix please. - int indent = 1; + private PubApi collectedApi = new PubApi(); - public PubapiVisitor(StringBuffer sb) { - this.sb = sb; - } - - String depth(int l) { - return " ".substring(0, l); + private boolean isNonPrivate(Element e) { + return !e.getModifiers().contains(PRIVATE); } @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitType(TypeElement e, Void p) { - if (e.getModifiers().contains(Modifier.PUBLIC) - || e.getModifiers().contains(Modifier.PROTECTED)) - { - sb.append(depth(indent) + "TYPE " + e.getQualifiedName() + "\n"); - indent += 2; - Void v = super.visitType(e, p); - indent -= 2; - return v; + if (isNonPrivate(e)) { + PubApi prevApi = collectedApi; + collectedApi = new PubApi(); + super.visitType(e, p); + PubType t = new PubType(e.getModifiers(), + e.getQualifiedName().toString(), + collectedApi); + prevApi.types.put(t.fqName, t); + collectedApi = prevApi; } return null; } + private static String encodeChar(int c) { + return String.format("\\u%04x", c); + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitVariable(VariableElement e, Void p) { - if (e.getModifiers().contains(Modifier.PUBLIC) - || e.getModifiers().contains(Modifier.PROTECTED)) { - sb.append(depth(indent)).append("VAR ") - .append(makeVariableString(e)).append("\n"); + if (isNonPrivate(e)) { + Object constVal = e.getConstantValue(); + String constValStr = null; + // TODO: This doesn't seem to be entirely accurate. What if I change + // from, say, 0 to 0L? (And the field is public final static so that + // it could get inlined.) + if (constVal != null) { + if (e.asType().toString().equals("char")) { + // What type is 'value'? Is it already a char? + char c = constVal.toString().charAt(0); + constValStr = "'" + encodeChar(c) + "'"; + } else { + constValStr = constVal.toString() + .chars() + .mapToObj(PubapiVisitor::encodeChar) + .collect(Collectors.joining("", "\"", "\"")); + } + } + + PubVar v = new PubVar(e.getModifiers(), + TypeDesc.fromType(e.asType()), + e.toString(), + constValStr); + collectedApi.variables.put(v.identifier, v); } + // Safe to not recurse here, because the only thing // to visit here is the constructor of a variable declaration. // If it happens to contain an anonymous inner class (which it might) @@ -91,70 +120,38 @@ @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitExecutable(ExecutableElement e, Void p) { - if (e.getModifiers().contains(Modifier.PUBLIC) - || e.getModifiers().contains(Modifier.PROTECTED)) { - sb.append(depth(indent)).append("METHOD ") - .append(makeMethodString(e)).append("\n"); + if (isNonPrivate(e)) { + PubMethod m = new PubMethod(e.getModifiers(), + getTypeParameters(e.getTypeParameters()), + TypeDesc.fromType(e.getReturnType()), + e.getSimpleName().toString(), + getTypeDescs(getParamTypes(e)), + getTypeDescs(e.getThrownTypes())); + collectedApi.methods.put(m.asSignatureString(), m); } return null; } - /** - * Creates a String representation of a method element with everything - * necessary to track all public aspects of it in an API. - * @param e Element to create String for. - * @return String representation of element. - */ - protected String makeMethodString(ExecutableElement e) { - StringBuilder result = new StringBuilder(); - for (Modifier modifier : e.getModifiers()) { - result.append(modifier.toString()); - result.append(" "); - } - result.append(e.getReturnType().toString()); - result.append(" "); - result.append(e.toString()); - - List thrownTypes = e.getThrownTypes(); - if (!thrownTypes.isEmpty()) { - result.append(" throws "); - for (Iterator iterator = thrownTypes - .iterator(); iterator.hasNext();) { - TypeMirror typeMirror = iterator.next(); - result.append(typeMirror.toString()); - if (iterator.hasNext()) { - result.append(", "); - } - } - } - return result.toString(); + private List getTypeParameters(List elements) { + return elements.stream() + .map(e -> new PubApiTypeParam(e.getSimpleName().toString(), getTypeDescs(e.getBounds()))) + .collect(Collectors.toList()); } - /** - * Creates a String representation of a variable element with everything - * necessary to track all public aspects of it in an API. - * @param e Element to create String for. - * @return String representation of element. - */ - protected String makeVariableString(VariableElement e) { - StringBuilder result = new StringBuilder(); - for (Modifier modifier : e.getModifiers()) { - result.append(modifier.toString()); - result.append(" "); - } - result.append(e.asType().toString()); - result.append(" "); - result.append(e.toString()); - Object value = e.getConstantValue(); - if (value != null) { - result.append(" = "); - if (e.asType().toString().equals("char")) { - int v = (int)value.toString().charAt(0); - result.append("'\\u"+Integer.toString(v,16)+"'"); - } else { - result.append(value.toString()); - } - } - return result.toString(); + private List getParamTypes(ExecutableElement e) { + return e.getParameters() + .stream() + .map(VariableElement::asType) + .collect(Collectors.toList()); + } + + private List getTypeDescs(List list) { + return list.stream() + .map(TypeDesc::fromType) + .collect(Collectors.toList()); + } + + public PubApi getCollectedPubApi() { + return collectedApi; } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java @@ -29,22 +29,20 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; -import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Set; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; -import com.sun.source.tree.CompilationUnitTree; import com.sun.tools.javac.api.JavacTaskImpl; import com.sun.tools.javac.api.JavacTool; -import com.sun.tools.javac.code.Symbol.ClassSymbol; -import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Options; +import com.sun.tools.sjavac.Log; import com.sun.tools.sjavac.Util; import com.sun.tools.sjavac.comp.dependencies.DependencyCollector; import com.sun.tools.sjavac.comp.dependencies.PublicApiCollector; @@ -62,7 +60,7 @@ * deletion without notice. */ public class SjavacImpl implements Sjavac { - +static Object lock = new Object(); @Override public SysInfo getSysInfo() { return new SysInfo(Runtime.getRuntime().availableProcessors(), @@ -76,86 +74,78 @@ List explicitSources, Set sourcesToCompile, Set visibleSources) { - JavacTool compiler = JavacTool.create(); - try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { - SmartFileManager smartFileManager = new SmartFileManager(fileManager); + + JavacTool compiler = (JavacTool) ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { + SmartFileManager sfm = new SmartFileManager(fm); Context context = new Context(); - // Now setup the actual compilation.... + // Now setup the actual compilation CompilationResult compilationResult = new CompilationResult(0); - // First deal with explicit source files on cmdline and in at file. - ListBuffer compilationUnits = new ListBuffer<>(); - for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(explicitSources)) { - compilationUnits.append(i); + // First deal with explicit source files on cmdline and in at file + ListBuffer explicitJFOs = new ListBuffer<>(); + for (JavaFileObject i : fm.getJavaFileObjectsFromFiles(explicitSources)) { + explicitJFOs.append(i); } - // Now deal with sources supplied as source_to_compile. + // Now deal with sources supplied as source_to_compile ListBuffer sourcesToCompileFiles = new ListBuffer<>(); - for (URI u : sourcesToCompile) { + for (URI u : sourcesToCompile) sourcesToCompileFiles.append(new File(u)); - } - for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) { - compilationUnits.append(i); - } - // Create a new logger. + for (JavaFileObject i : fm.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) + explicitJFOs.append(i); + + // Create a new logger StringWriter stdoutLog = new StringWriter(); StringWriter stderrLog = new StringWriter(); PrintWriter stdout = new PrintWriter(stdoutLog); PrintWriter stderr = new PrintWriter(stderrLog); com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK; - DependencyCollector depsCollector = new DependencyCollector(); - PublicApiCollector pubApiCollector = new PublicApiCollector(); + DependencyCollector depsCollector = new DependencyCollector(context); + PublicApiCollector pubApiCollector = new PublicApiCollector(context); PathAndPackageVerifier papVerifier = new PathAndPackageVerifier(); try { - if (compilationUnits.size() > 0) { - smartFileManager.setVisibleSources(visibleSources); - smartFileManager.cleanArtifacts(); - smartFileManager.setLog(stdout); + if (explicitJFOs.size() > 0) { + sfm.setVisibleSources(visibleSources); + sfm.cleanArtifacts(); + sfm.setLog(stdout); // Do the compilation! JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(stderr, - smartFileManager, + sfm, null, Arrays.asList(args), null, - compilationUnits, + explicitJFOs, context); - smartFileManager.setSymbolFileEnabled(!Options.instance(context).isSet("ignore.symbol.file")); + sfm.setSymbolFileEnabled(!Options.instance(context).isSet("ignore.symbol.file")); task.addTaskListener(depsCollector); task.addTaskListener(pubApiCollector); task.addTaskListener(papVerifier); + Log.debug("Invoking javac with args"); + Arrays.asList(args).forEach(arg -> Log.debug(" " + arg)); rc = task.doCall(); - smartFileManager.flush(); + Log.debug("javac returned with code " + rc); + sfm.flush(); } } catch (Exception e) { + Log.error(Util.getStackTrace(e)); stderrLog.append(Util.getStackTrace(e)); rc = com.sun.tools.javac.main.Main.Result.ERROR; } - compilationResult.packageArtifacts = smartFileManager.getPackageArtifacts(); + compilationResult.packageArtifacts = sfm.getPackageArtifacts(); + + if (papVerifier.errorsDiscovered()) + rc = com.sun.tools.javac.main.Main.Result.ERROR; Dependencies deps = Dependencies.instance(context); - for (PackageSymbol from : depsCollector.getSourcePackages()) { - for (PackageSymbol to : depsCollector.getDependenciesForPkg(from)) - deps.collect(from.fullname, to.fullname); - } - - for (ClassSymbol cs : pubApiCollector.getClassSymbols()) - deps.visitPubapi(cs); - - if (papVerifier.getMisplacedCompilationUnits().size() > 0) { - for (CompilationUnitTree cu : papVerifier.getMisplacedCompilationUnits()) { - System.err.println("Misplaced compilation unit."); - System.err.println(" Directory: " + Paths.get(cu.getSourceFile().toUri()).getParent()); - System.err.println(" Package: " + cu.getPackageName()); - } - rc = com.sun.tools.javac.main.Main.Result.ERROR; - } - - compilationResult.packageDependencies = deps.getDependencies(); - compilationResult.packagePubapis = deps.getPubapis(); + compilationResult.packageDependencies = deps.getDependencies(explicitJFOs); + compilationResult.packageCpDependencies = deps.getCpDependencies(explicitJFOs); + compilationResult.packagePubapis = deps.getPubapis(explicitJFOs, true); + compilationResult.dependencyPubapis = deps.getPubapis(explicitJFOs, false); compilationResult.stdout = stdoutLog.toString(); compilationResult.stderr = stderrLog.toString(); compilationResult.returnCode = rc.exitCode; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SmartFileManager.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SmartFileManager.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SmartFileManager.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SmartFileManager.java @@ -91,6 +91,12 @@ ((JavacFileManager) fileManager).setSymbolFileEnabled(b); } + @DefinedBy(Api.COMPILER) + public String inferBinaryName(Location location, JavaFileObject file) { + return super.inferBinaryName(location, locUnwrap(file)); + } + + public Map> getPackageArtifacts() { return packageArtifacts; } @@ -100,10 +106,11 @@ String packageName, Set kinds, boolean recurse) throws IOException { + // TODO: Do this lazily by returning an iterable with a filtering Iterator // Acquire the list of files. Iterable files = super.list(location, packageName, kinds, recurse); if (visibleSources.isEmpty()) { - return files; + return locWrapMany(files, location); } // Now filter! ListBuffer filteredFiles = new ListBuffer<>(); @@ -116,12 +123,8 @@ filteredFiles.add(f); } } - return filteredFiles; - } - @Override @DefinedBy(Api.COMPILER) - public boolean hasLocation(Location location) { - return super.hasLocation(location); + return locWrapMany(filteredFiles, location); } @Override @DefinedBy(Api.COMPILER) @@ -129,6 +132,7 @@ String className, Kind kind) throws IOException { JavaFileObject file = super.getJavaFileForInput(location, className, kind); + file = locWrap(file, location); if (file == null || visibleSources.isEmpty()) { return file; } @@ -145,6 +149,7 @@ Kind kind, FileObject sibling) throws IOException { JavaFileObject file = super.getJavaFileForOutput(location, className, kind, sibling); + file = locWrap(file, location); if (file == null) return file; int dp = className.lastIndexOf('.'); String pkg_name = ""; @@ -162,6 +167,7 @@ String packageName, String relativeName) throws IOException { FileObject file = super.getFileForInput(location, packageName, relativeName); + file = locWrap(file, location); if (file == null || visibleSources.isEmpty()) { return file; } @@ -177,11 +183,12 @@ String packageName, String relativeName, FileObject sibling) throws IOException { - FileObject file = super.getFileForOutput(location, packageName, relativeName, sibling); + FileObject superFile = super.getFileForOutput(location, packageName, relativeName, sibling); + FileObject file = locWrap(superFile, location); if (file == null) return file; - if (location.equals(StandardLocation.NATIVE_HEADER_OUTPUT) && - file instanceof JavaFileObject) { - file = new SmartFileObject((JavaFileObject)file, stdout); + + if (location.equals(StandardLocation.NATIVE_HEADER_OUTPUT) && superFile instanceof JavaFileObject) { + file = new SmartFileObject((JavaFileObject) file, stdout); packageName = ":" + packageNameFromFileName(relativeName); } if (packageName.equals("")) { @@ -191,7 +198,7 @@ return file; } - private String packageNameFromFileName(String fn) { + private static String packageNameFromFileName(String fn) { StringBuilder sb = new StringBuilder(); int p = fn.indexOf('_'), pp = 0; while (p != -1) { @@ -204,16 +211,6 @@ return sb.toString(); } - @Override @DefinedBy(Api.COMPILER) - public void flush() throws IOException { - super.flush(); - } - - @Override @DefinedBy(Api.COMPILER) - public void close() throws IOException { - super.close(); - } - void addArtifact(String pkgName, URI art) { Set s = packageArtifacts.get(pkgName); if (s == null) { @@ -222,4 +219,42 @@ } s.add(art); } + + private static JavaFileObject locWrap(JavaFileObject jfo, Location loc) { + return jfo == null ? null : new JavaFileObjectWithLocation<>(jfo, loc); + } + + private static FileObject locWrap(FileObject fo, Location loc) { + if (fo instanceof JavaFileObject) + return locWrap((JavaFileObject) fo, loc); + return fo == null ? null : new FileObjectWithLocation<>(fo, loc); + } + + @DefinedBy(Api.COMPILER) + @Override + public boolean isSameFile(FileObject a, FileObject b) { + return super.isSameFile(locUnwrap(a), locUnwrap(b)); + } + + private static ListBuffer locWrapMany(Iterable jfos, + Location loc) { + ListBuffer locWrapped = new ListBuffer<>(); + for (JavaFileObject f : jfos) + locWrapped.add(locWrap(f, loc)); + return locWrapped; + } + + private static FileObject locUnwrap(FileObject fo) { + if (fo instanceof FileObjectWithLocation) + return ((FileObjectWithLocation) fo).getDelegate(); + if (fo instanceof JavaFileObjectWithLocation) + return ((JavaFileObjectWithLocation) fo).getDelegate(); + return fo; + } + + private static JavaFileObject locUnwrap(JavaFileObject fo) { + if (fo instanceof JavaFileObjectWithLocation) + return ((JavaFileObjectWithLocation) fo).getDelegate(); + return fo; + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/Dependency.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/Dependency.java deleted file mode 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/Dependency.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2014, 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.comp.dependencies; - -import java.util.Set; - -import com.sun.tools.javac.code.Symbol.PackageSymbol; - -interface Dependency { - Set getPackages(); -} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyCollector.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyCollector.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyCollector.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyCollector.java @@ -24,26 +24,38 @@ */ package com.sun.tools.sjavac.comp.dependencies; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; + import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; -import com.sun.tools.sjavac.Util; +import com.sun.tools.sjavac.Log; +import com.sun.tools.sjavac.comp.Dependencies; +import com.sun.tools.sjavac.comp.JavaFileObjectWithLocation; public class DependencyCollector implements TaskListener { - Map> collectedDependencies = new HashMap<>(); + private Context context; - @Override - @DefinedBy(Api.COMPILER_TREE) - public void started(TaskEvent e) { + // Compilation unit -> fully qualified name -> class symbol + public final Map>> collectedDependencies = new HashMap<>(); + + public DependencyCollector(Context context) { + this.context = context; } @Override @@ -51,26 +63,72 @@ public void finished(TaskEvent e) { if (e.getKind() == TaskEvent.Kind.ANALYZE) { JCCompilationUnit cu = (JCCompilationUnit) e.getCompilationUnit(); + //com.sun.tools.sjavac.Log.info("Finished compilation unit: " + cu.sourcefile); PackageSymbol thisPkg = cu.packge; if (thisPkg == null) { // Compilation unit in default package. See JDK-8048144. return; } - DependencyScanner ds = new DependencyScanner(); + DependencyScanner ds = new DependencyScanner(context); cu.accept(ds); - Set pkgDeps = ds.getResult() - .stream() - .flatMap(dep -> dep.getPackages().stream()) - .collect(Collectors.toSet()); - collectedDependencies.merge(thisPkg, pkgDeps, Util::union); + + collectedDependencies.put(cu, ds.dependencies); + } + + if (e.getKind() == TaskEvent.Kind.COMPILATION) { + Dependencies deps = Dependencies.instance(context); + + // Collect dependencies + for (JCCompilationUnit fromCu : collectedDependencies.keySet()) { + + // fqName -> set of type deps + Map> map = collectedDependencies.get(fromCu); + for (String fromFqName : map.keySet()) { + Set toSymbols = map.get(fromFqName); + Set toOutermostSymbols = collectOutermostClassSymbols(toSymbols); + + for (ClassSymbol toTypeSymbol : toOutermostSymbols) { + + // Ignore self-dependencies + if (fromFqName.equals(toTypeSymbol.flatname.toString())) + continue; + Location loc = getLocationOf(toTypeSymbol); + if (loc != StandardLocation.PLATFORM_CLASS_PATH) + deps.collect(loc, fromCu, fromFqName, toTypeSymbol); + } + } + } + + // Collect pubapi of dependencies + Collection>> allDepMaps = collectedDependencies.values(); + for (Map> depMap : allDepMaps) { + for (String fromFqName : depMap.keySet()) { + Set toSymbols = depMap.get(fromFqName); + Set toOutermostSymbols = collectOutermostClassSymbols(toSymbols); + for (ClassSymbol ts : toOutermostSymbols) { + // Ignore self-dependencies + if (fromFqName.equals(ts.flatname.toString())) + continue; + if (getLocationOf(ts) != StandardLocation.PLATFORM_CLASS_PATH) + deps.visitPubapi(ts); + } + } + } + } } - public Set getSourcePackages() { - return collectedDependencies.keySet(); + private Location getLocationOf(ClassSymbol cs) { + JavaFileObject jfo = cs.outermostClass().classfile; + if (jfo != null) + return ((JavaFileObjectWithLocation) jfo).getLocation(); + Log.warn("Could not determine location of " + cs.flatName()); + return null; } - public Set getDependenciesForPkg(PackageSymbol ps) { - return collectedDependencies.get(ps); + private Set collectOutermostClassSymbols(Set cs) { + return cs.stream() + .map(Symbol::outermostClass) + .collect(Collectors.toSet()); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyScanner.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyScanner.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/DependencyScanner.java @@ -23,26 +23,49 @@ * questions. */ package com.sun.tools.sjavac.comp.dependencies; - +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.tree.JCTree.JCPackageDecl; import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; +import com.sun.tools.sjavac.Util; class DependencyScanner extends TreeScanner { - public final Set dependencies = new HashSet<>(); + private final ClassSymbol OBJECT_SYM; + private String currentClass = null; + + public final Map> dependencies = new HashMap<>(); + public final Map> lazyImports = new HashMap<>(); + + // Keep track of imported classes, since they should be reported as dependency of any subsequent class decls. (This may change when moving to CompilationUnits). + private final Set typesImported = new HashSet<>(); + private final Set lazyImportedPkgs = new HashSet<>(); + + public DependencyScanner(Context context) { + OBJECT_SYM = (ClassSymbol) Symtab.instance(context).objectType.tsym; + } private boolean isValidDependency(Type t) { if (t == null || t.isPrimitiveOrVoid() || t.isErroneous()) return false; + if (!(t.tsym instanceof ClassSymbol)) + return false; TypeTag tag = t.getTag(); return tag != TypeTag.PACKAGE && tag != TypeTag.METHOD @@ -51,34 +74,112 @@ } @Override + public void visitClassDef(JCClassDecl tree) { + // Skip anonymous classes for now. (Not sure if non-top level classes + // are interesting at this point) + if (tree.sym == null) + return; + + String prevClass = currentClass; + currentClass = tree.sym.flatname.toString(); + // Make sure the currentClass depends on the imported stuff. (This may go away when moving to CompilationUnits.) + typesImported.forEach(this::addDep); + lazyImports.put(currentClass, lazyImportedPkgs); + + // Make sure current class depends on Object if no extends expression is provided + if (tree.extending == null) + addDep(OBJECT_SYM); + else + addAllDeps(allSupertypes((ClassSymbol) tree.extending.type.tsym)); + super.visitClassDef(tree); + currentClass = prevClass; + } + + @Override + public void visitPackageDef(JCPackageDecl tree) { + // Ignore identifiers found in the package declaration. + //super.visitPackageDef(tree); + } + + @Override public void visitIdent(JCIdent tree) { if (isValidDependency(tree.type)) - dependencies.add(new TypeAndSupertypesDependency(tree.type.tsym)); + addDep((ClassSymbol) tree.type.tsym); super.visitIdent(tree); } @Override + public void visitImport(JCImport inport) { + TreeScanner importVisitor = new TreeScanner() { + @Override + public void visitSelect(JCFieldAccess tree) { + if (tree.getIdentifier().contentEquals("*")) { + Symbol sym = tree.selected instanceof JCIdent ? ((JCIdent) tree.selected).sym + : ((JCFieldAccess) tree.selected).sym; + if (sym instanceof ClassSymbol) { + typesImported.add((ClassSymbol) sym); + } else if (sym instanceof PackageSymbol) { + lazyImports.merge(currentClass, Collections.singleton((PackageSymbol) sym), Util::union); + } else { + Assert.error("Unknown import symbol: " + sym); + } + } else { + // If this is a static import, the sym is null (why?) so + // overapproximate by going up one level. + if (inport.isStatic()) + tree = ((JCFieldAccess) tree.selected); + typesImported.add((ClassSymbol) tree.sym); + } + } + }; + inport.accept(importVisitor); + + // Avoid further visitSelect etc since we have no currentClass. + // super.visitImport(tree); + } + + @Override public void visitSelect(JCFieldAccess tree) { - if (tree.getIdentifier().contentEquals("*")) { - Symbol sym = tree.selected instanceof JCIdent ? ((JCIdent) tree.selected).sym - : ((JCFieldAccess) tree.selected).sym; - if (sym instanceof ClassSymbol) { - ClassSymbol clsSym = (ClassSymbol) sym; - dependencies.add(new TypeAndSupertypesDependency(clsSym.type.tsym)); - } else { - dependencies.add(new PackageDependency((PackageSymbol) sym)); - } - } else if (tree.type != null && tree.type.hasTag(TypeTag.METHOD)) { // Method call? Depend on the result (even though we never access it elsewhere) + + // This is not entirely safe. @MyAnno(SomeClass.class) public class C {} should depend on SomeClass.class + if (currentClass == null) + return; + + if (tree.type != null && tree.type.hasTag(TypeTag.METHOD)) { // Method call? Depend on the result (even though we never access it elsewhere) Type retType = tree.type.getReturnType(); if (isValidDependency(retType)) - dependencies.add(new TypeAndSupertypesDependency(retType.tsym)); + addDep((ClassSymbol) retType.tsym); } else if (isValidDependency(tree.type)) { - dependencies.add(new TypeAndSupertypesDependency(tree.type.tsym)); + ClassSymbol cs = (ClassSymbol) tree.type.tsym; + addDep(cs); + addAllDeps(allSupertypes(cs)); } super.visitSelect(tree); } - public Set getResult() { - return dependencies; + private Set allSupertypes(ClassSymbol cs) { + if (cs == null) + return Collections.emptySet(); + Set result = new HashSet<>(); + result.add(cs); + if (cs instanceof ClassSymbol) { + result.addAll(allSupertypes((ClassSymbol) cs.getSuperclass().tsym)); + for (Type it : cs.getInterfaces()) + result.addAll(allSupertypes((ClassSymbol) it.tsym)); + } + return result; } + + private void addDep(ClassSymbol tsym) { + if (currentClass == null) + throw new RuntimeException("currentClass == null"); + if (tsym == null) + throw new RuntimeException("sym == null"); + dependencies.merge(currentClass, Collections.singleton(tsym), Util::union); + } + + private void addAllDeps(Set tsyms) { + dependencies.merge(currentClass, tsyms, Util::union); + } + } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PackageDependency.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PackageDependency.java deleted file mode 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PackageDependency.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2014, 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.comp.dependencies; - -import java.util.Collections; -import java.util.Set; - -import com.sun.tools.javac.code.Symbol.PackageSymbol; - -public class PackageDependency implements Dependency { - PackageSymbol ps; - public PackageDependency(PackageSymbol ps) { - this.ps = ps; - } - @Override - public Set getPackages() { - return Collections.singleton(ps); - } -} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java @@ -32,30 +32,49 @@ import com.sun.source.util.TaskListener; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; +import com.sun.tools.sjavac.comp.Dependencies; public class PublicApiCollector implements TaskListener { - final Set classSymbols = new HashSet<>(); + private Context context; + private final Set classSymbols = new HashSet<>(); - @Override - @DefinedBy(Api.COMPILER_TREE) - public void started(TaskEvent e) { + public PublicApiCollector(Context context) { + this.context = context; } @Override @DefinedBy(Api.COMPILER_TREE) public void finished(TaskEvent e) { - if (e.getKind() == TaskEvent.Kind.ANALYZE) { - for (Tree t : e.getCompilationUnit().getTypeDecls()) { - if (t instanceof JCClassDecl) // Can also be a JCSkip - classSymbols.add(((JCClassDecl) t).sym); - } + switch (e.getKind()) { + case ANALYZE: + collectClassSymbols((JCCompilationUnit) e.getCompilationUnit()); + break; + case COMPILATION: + extractPubApis(); + break; } } - public Set getClassSymbols() { - return classSymbols; + private void collectClassSymbols(JCCompilationUnit cu) { + for (Tree t : cu.getTypeDecls()) { + if (t instanceof JCClassDecl) // Can also be a JCSkip + classSymbols.add(((JCClassDecl) t).sym); + } + } + + private void extractPubApis() { + // For incremental builds we need to remember the public api of what + // we depend upon. + // Within a single compilation loop, we need to keep track of public + // api of what we're compiling to decide if any dependants needs to + // be tainted. + Dependencies deps = Dependencies.instance(context); + for (ClassSymbol cs : classSymbols) + deps.visitPubapi(cs); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/TypeAndSupertypesDependency.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/TypeAndSupertypesDependency.java deleted file mode 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/TypeAndSupertypesDependency.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2014, 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.comp.dependencies; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import com.sun.tools.javac.code.Symbol.ClassSymbol; -import com.sun.tools.javac.code.Symbol.PackageSymbol; -import com.sun.tools.javac.code.Symbol.TypeSymbol; -import com.sun.tools.javac.code.Kinds; -import com.sun.tools.javac.code.Type; - -import static com.sun.tools.javac.code.Kinds.Kind.*; - -public class TypeAndSupertypesDependency implements Dependency { - - protected TypeSymbol type; - - public TypeAndSupertypesDependency(TypeSymbol type) { - this.type = Objects.requireNonNull(type); - } - - private Set allSupertypes(TypeSymbol t) { - if (t == null) - return Collections.emptySet(); - Set result = new HashSet<>(); - result.add(t); - if (t instanceof ClassSymbol) { - ClassSymbol cs = (ClassSymbol) t; - result.addAll(allSupertypes(cs.getSuperclass().tsym)); - for (Type it : cs.getInterfaces()) - result.addAll(allSupertypes(it.tsym)); - } - return result; - } - - @Override - public Set getPackages() { - if (type.kind == ERR) - return Collections.emptySet(); - if (type instanceof ClassSymbol) { - return allSupertypes(type).stream() - .map(TypeSymbol::packge) - .collect(Collectors.toSet()); - } - throw new AssertionError("Could not get package name for " + type); - } -} - diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/options/Options.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/options/Options.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/options/Options.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/options/Options.java @@ -36,6 +36,7 @@ import java.util.HashSet; import com.sun.tools.sjavac.Transformer; +import com.sun.tools.sjavac.Util; /** * Instances of this class represent values for sjavac command line options. @@ -286,6 +287,8 @@ public String[] prepJavacArgs() { List args = new ArrayList<>(); +//args.add("-doe"); + // Output directories args.add("-d"); args.add(destDir.toString()); @@ -318,6 +321,18 @@ // This can't be anything but 'none'. Enforced by sjavac main method. args.add("-implicit:" + implicitPolicy); + // If this option is not used, Object for instance is picked up from + // PLATFORM_CLASS_PATH. + // + // Turns out that bootclasspath = classpath, in the JDK build so it's + // still actually the correct file that is looked up. However, we don't + // want non-JDK-builds to track files on the bootclasspath, so we enable + // this option, to allow us to ignore dependencies on bootclasspath. + // + // It may make more sense to use an empty bootclasspath when building + // the JDK. + //args.add("-XXuserPathsFirst"); + // Append javac-options (i.e. pass through options not recognized by // sjavac to javac.) args.addAll(javacArgs); @@ -358,21 +373,25 @@ @Override public void exclude(String exclPattern) { + exclPattern = Util.normalizeDriveLetter(exclPattern); excludes.add(exclPattern); } @Override public void include(String inclPattern) { + inclPattern = Util.normalizeDriveLetter(inclPattern); includes.add(inclPattern); } @Override public void excludeFile(String exclFilePattern) { + exclFilePattern = Util.normalizeDriveLetter(exclFilePattern); excludeFiles.add(exclFilePattern); } @Override public void includeFile(String inclFilePattern) { + inclFilePattern = Util.normalizeDriveLetter(inclFilePattern); includeFiles.add(inclFilePattern); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ArrayTypeDesc.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ArrayTypeDesc.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ArrayTypeDesc.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; + +import javax.lang.model.type.TypeKind; + +public class ArrayTypeDesc extends TypeDesc implements Serializable { + + private static final long serialVersionUID = -1177329549163314996L; + + TypeDesc compTypeDesc; + + public ArrayTypeDesc(TypeDesc compTypeDesc) { + super(TypeKind.ARRAY); + this.compTypeDesc = compTypeDesc; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) + return false; + return compTypeDesc.equals(((ArrayTypeDesc) obj).compTypeDesc); + } + + @Override + public int hashCode() { + return super.hashCode() ^ compTypeDesc.hashCode(); + } + +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PrimitiveTypeDesc.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PrimitiveTypeDesc.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PrimitiveTypeDesc.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; + +import javax.lang.model.type.TypeKind; + +public class PrimitiveTypeDesc extends TypeDesc implements Serializable { + + private static final long serialVersionUID = 6051065543149129106L; + + public PrimitiveTypeDesc(TypeKind typeKind) { + super(typeKind); + if (!typeKind.isPrimitive() && typeKind != TypeKind.VOID) + throw new IllegalArgumentException("Only primitives or void accepted"); + } + + // This class has no fields, so the inherited hashCode and equals should do fine. +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApi.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApi.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApi.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2014, 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.pubapi; + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import static com.sun.tools.sjavac.Util.*; + +import javax.lang.model.element.Modifier; + +import com.sun.tools.javac.util.Assert; + +public class PubApi implements Serializable { + + private static final long serialVersionUID = 5926627347801986850L; + + // Used to have Set here. Problem is that the objects are mutated during + // javac_state loading, causing them to change hash codes. We could probably + // change back to Set once javac_state loading is cleaned up. + public final Map types = new HashMap<>(); + public final Map variables = new HashMap<>(); + public final Map methods = new HashMap<>(); + + // Currently this is implemented as equality. This is far from optimal. It + // should preferably make sure that all previous methods are still available + // and no abstract methods are added. It should also be aware of inheritance + // of course. + public boolean isBackwardCompatibleWith(PubApi older) { + return equals(older); + } + + private static String typeLine(PubType type) { + return String.format("TYPE %s%s", asString(type.modifiers), type.fqName); + } + + private static String varLine(PubVar var) { + return String.format("VAR %s%s %s%s", + asString(var.modifiers), + TypeDesc.encodeAsString(var.type), + var.identifier, + var.getConstValue().map(v -> " = " + v).orElse("")); + } + + private static String methodLine(PubMethod method) { + return String.format("METHOD %s%s%s %s(%s)%s", + asString(method.modifiers), + method.typeParams.isEmpty() ? "" : ("<" + method.typeParams.stream().map(PubApiTypeParam::asString).collect(Collectors.joining(",")) + "> "), + TypeDesc.encodeAsString(method.returnType), + method.identifier, + commaSeparated(method.paramTypes), + method.throwDecls.isEmpty() + ? "" + : " throws " + commaSeparated(method.throwDecls)); + } + + public List asListOfStrings() { + List lines = new ArrayList<>(); + + // Types + types.values() + .stream() + .sorted(Comparator.comparing(PubApi::typeLine)) + .forEach(type -> { + lines.add(typeLine(type)); + for (String subline : type.pubApi.asListOfStrings()) + lines.add(" " + subline); + }); + + // Variables + variables.values() + .stream() + .map(PubApi::varLine) + .sorted() + .forEach(lines::add); + + // Methods + methods.values() + .stream() + .map(PubApi::methodLine) + .sorted() + .forEach(lines::add); + + return lines; + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PubApi other = (PubApi) obj; + return types.equals(other.types) + && variables.equals(other.variables) + && methods.equals(other.methods); + } + + @Override + public int hashCode() { + return types.keySet().hashCode() + ^ variables.keySet().hashCode() + ^ methods.keySet().hashCode(); + } + + private static String commaSeparated(List typeDescs) { + return typeDescs.stream() + .map(TypeDesc::encodeAsString) + .collect(Collectors.joining(",")); + } + + // Create space separated list of modifiers (with a trailing space) + private static String asString(Set modifiers) { + return modifiers.stream() + .map(mod -> mod + " ") + .sorted() + .collect(Collectors.joining()); + } + + // Used to combine class PubApis to package level PubApis + public static PubApi mergeTypes(PubApi api1, PubApi api2) { + Assert.check(api1.methods.isEmpty(), "Can only merge types."); + Assert.check(api2.methods.isEmpty(), "Can only merge types."); + Assert.check(api1.variables.isEmpty(), "Can only merge types."); + Assert.check(api2.variables.isEmpty(), "Can only merge types."); + PubApi merged = new PubApi(); + merged.types.putAll(api1.types); + merged.types.putAll(api2.types); + return merged; + } + + + // Used for line-by-line parsing + private PubType lastInsertedType = null; + private final static String MODIFIERS = Arrays.asList(Modifier.values()).stream().map(m -> m.name().toLowerCase(Locale.US)).collect(Collectors.joining("|", "(", ")")); + private final static Pattern MOD_PATTERN = Pattern.compile("(" + MODIFIERS + " )*"); + private final static Pattern METHOD_PATTERN = Pattern.compile("(?.+?) (?\\S+)\\((?.*)\\)( throws (?.*))?"); + private final static Pattern VAR_PATTERN = Pattern.compile("VAR (?("+MODIFIERS+" )*)(?.+?) (?\\S+)( = (?.*))?"); + private final static Pattern TYPE_PATTERN = Pattern.compile("TYPE (?("+MODIFIERS+" )*)(?\\S+)"); + + public void appendItem(String l) { + try { + if (l.startsWith(" ")) { + lastInsertedType.pubApi.appendItem(l.substring(2)); + return; + } + + if (l.startsWith("METHOD")) { + l = l.substring("METHOD ".length()); + Set modifiers = new HashSet<>(); + Matcher modMatcher = MOD_PATTERN.matcher(l); + if (modMatcher.find()) { + String modifiersStr = modMatcher.group(); + modifiers.addAll(parseModifiers(modifiersStr)); + l = l.substring(modifiersStr.length()); + } + List typeParams = new ArrayList<>(); + if (l.startsWith("<")) { + int closingPos = findClosingTag(l, 0); + String str = l.substring(1, closingPos); + l = l.substring(closingPos+1); + typeParams.addAll(parseTypeParams(splitOnTopLevelCommas(str))); + } + Matcher mm = METHOD_PATTERN.matcher(l); + if (!mm.matches()) + throw new AssertionError("Could not parse return type, identifier, parameter types or throws declaration of method: " + l); + + List params = splitOnTopLevelCommas(mm.group("params")); + String th = Optional.ofNullable(mm.group("throws")).orElse(""); + List throwz = splitOnTopLevelCommas(th); + PubMethod m = new PubMethod(modifiers, + typeParams, + TypeDesc.decodeString(mm.group("ret")), + mm.group("name"), + parseTypeDescs(params), + parseTypeDescs(throwz)); + methods.put(m.asSignatureString(), m); + return; + } + + Matcher vm = VAR_PATTERN.matcher(l); + if (vm.matches()) { + PubVar v = new PubVar(parseModifiers(vm.group("modifiers")), + TypeDesc.decodeString(vm.group("type")), + vm.group("id"), + vm.group("val")); + variables.put(v.identifier, v); + return; + } + + Matcher tm = TYPE_PATTERN.matcher(l); + if (tm.matches()) { + lastInsertedType = new PubType(parseModifiers(tm.group("modifiers")), + tm.group("fullyQualified"), + new PubApi()); + types.put(lastInsertedType.fqName, lastInsertedType); + return; + } + + throw new AssertionError("No matching line pattern."); + } catch (Throwable e) { + throw new AssertionError("Could not parse API line: " + l, e); + } + } + + private static List parseTypeDescs(List strs) { + return strs.stream() + .map(TypeDesc::decodeString) + .collect(Collectors.toList()); + } + + private static List parseTypeParams(List strs) { + return strs.stream().map(PubApi::parseTypeParam).collect(Collectors.toList()); + } + + // Parse a type parameter string. Example input: + // identifier + // identifier extends Type (& Type)* + private static PubApiTypeParam parseTypeParam(String typeParamString) { + int extPos = typeParamString.indexOf(" extends "); + if (extPos == -1) + return new PubApiTypeParam(typeParamString, Collections.emptyList()); + String identifier = typeParamString.substring(0, extPos); + String rest = typeParamString.substring(extPos + " extends ".length()); + List bounds = parseTypeDescs(splitOnTopLevelChars(rest, '&')); + return new PubApiTypeParam(identifier, bounds); + } + + public Set parseModifiers(String modifiers) { + if (modifiers == null) + return Collections.emptySet(); + return Arrays.asList(modifiers.split(" ")) + .stream() + .map(PubApi::trimToUpper) + .filter(s -> !s.isEmpty()) + .map(Modifier::valueOf) + .collect(Collectors.toSet()); + } + + public static String trimToUpper(String s) { + return s.trim().toUpperCase(Locale.US); + } + + // Find closing tag of the opening tag at the given 'pos'. + private static int findClosingTag(String l, int pos) { + while (true) { + pos = pos + 1; + if (l.charAt(pos) == '>') + return pos; + if (l.charAt(pos) == '<') + pos = findClosingTag(l, pos); + } + } + + public List splitOnTopLevelCommas(String s) { + return splitOnTopLevelChars(s, ','); + } + + public static List splitOnTopLevelChars(String s, char split) { + if (s.isEmpty()) + return Collections.emptyList(); + List result = new ArrayList<>(); + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (char c : s.toCharArray()) { + if (c == split && depth == 0) { + result.add(buf.toString().trim()); + buf = new StringBuilder(); + } else { + if (c == '<') depth++; + if (c == '>') depth--; + buf.append(c); + } + } + result.add(buf.toString().trim()); + return result; + } + + public boolean isEmpty() { + return types.isEmpty() && variables.isEmpty() && methods.isEmpty(); + } + + public String diff(PubApi prevApi) { + List diffs = new ArrayList<>(); + + for (String addedTypeKey : subtract(types.keySet(), prevApi.types.keySet())) + diffs.add("Type " + addedTypeKey + " was added."); + for (String removedTypeKey : subtract(prevApi.types.keySet(), types.keySet())) + diffs.add("Type " + removedTypeKey + " was removed."); + + for (String addedVarKey : subtract(variables.keySet(), prevApi.variables.keySet())) + diffs.add("Variable " + addedVarKey + " was added."); + for (String removedVarKey : subtract(prevApi.variables.keySet(), variables.keySet())) + diffs.add("Variable " + removedVarKey + " was removed."); + + for (String addedMethodKey : subtract(methods.keySet(), prevApi.methods.keySet())) + diffs.add("Method " + addedMethodKey + " was added."); + for (String removedMethodKey : subtract(prevApi.methods.keySet(), methods.keySet())) + diffs.add("Method " + removedMethodKey + " was removed."); + + if (diffs.isEmpty()) + return null; + + String result = diffs.get(0); + if (diffs.size() > 1) + result += " (and " + (diffs.size() - 1) + " other differences)"; + return result; + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApiTypeParam.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApiTypeParam.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubApiTypeParam.java @@ -0,0 +1,49 @@ +package com.sun.tools.sjavac.pubapi; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +public class PubApiTypeParam implements Serializable { + + private static final long serialVersionUID = 8899204612014329162L; + + private final String identifier; + private final List bounds; + + public PubApiTypeParam(String identifier, List bounds) { + this.identifier = identifier; + this.bounds = bounds; + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PubApiTypeParam other = (PubApiTypeParam) obj; + return identifier.equals(other.identifier) + && bounds.equals(other.bounds); + } + + @Override + public int hashCode() { + return identifier.hashCode() ^ bounds.hashCode(); + } + + public String asString() { + if (bounds.isEmpty()) + return identifier; + String boundsStr = bounds.stream() + .map(TypeDesc::encodeAsString) + .collect(Collectors.joining(" & ")); + return identifier + " extends " + boundsStr; + } + + @Override + public String toString() { + return String.format("%s[id: %s, bounds: %s]", + getClass().getSimpleName(), + identifier, + bounds); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubMethod.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubMethod.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Modifier; + +public class PubMethod implements Serializable { + + private static final long serialVersionUID = -7813050194553446243L; + + Set modifiers; + List typeParams; + TypeDesc returnType; + String identifier; + List paramTypes; + List throwDecls; + + public PubMethod(Set modifiers, + List typeParams, + TypeDesc returnType, + String identifier, + List paramTypes, + List throwDecls) { + this.modifiers = modifiers; + this.typeParams = typeParams; + this.returnType = returnType; + this.identifier = identifier; + this.paramTypes = paramTypes; + this.throwDecls = throwDecls; + } + + // We need to include return type and type parameters to be sure to have + // different values for different methods. (A method can be overloaded with + // the only difference being the upper bound of the return type.) + public String asSignatureString() { + StringBuilder sb = new StringBuilder(); + + // + if (typeParams.size() > 0) { + sb.append(typeParams.stream() + .map(PubApiTypeParam::asString) + .collect(Collectors.joining(",", "<", "> "))); + } + sb.append(TypeDesc.encodeAsString(returnType)); + sb.append(" "); + sb.append(identifier); + sb.append("("); + sb.append(paramTypes.stream() + .map(TypeDesc::encodeAsString) + .collect(Collectors.joining(","))); + sb.append(")"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PubMethod other = (PubMethod) obj; + return modifiers.equals(other.modifiers) + && typeParams.equals(other.typeParams) + && returnType.equals(other.returnType) + && identifier.equals(other.identifier) + && paramTypes.equals(other.paramTypes) + && throwDecls.equals(other.throwDecls); + } + + @Override + public int hashCode() { + return modifiers.hashCode() + ^ typeParams.hashCode() + ^ returnType.hashCode() + ^ identifier.hashCode() + ^ paramTypes.hashCode() + ^ throwDecls.hashCode(); + } + + public String toString() { + return String.format("%s[modifiers: %s, typeParams: %s, retType: %s, identifier: %s, params: %s, throws: %s]", + getClass().getSimpleName(), + modifiers, + typeParams, + returnType, + identifier, + paramTypes, + throwDecls); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubType.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubType.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubType.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; +import java.util.Set; + +import javax.lang.model.element.Modifier; + +public class PubType implements Serializable { + + private static final long serialVersionUID = -7423416049253889793L; + + public final Set modifiers; + public final String fqName; + public final PubApi pubApi; + + public PubType(Set modifiers, + String fqName, + PubApi pubApi) { + this.modifiers = modifiers; + this.fqName = fqName; + this.pubApi = pubApi; + } + + public String getFqName() { + return fqName.toString(); + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PubType other = (PubType) obj; + return modifiers.equals(other.modifiers) + && fqName.equals(other.fqName) + && pubApi.equals(other.pubApi); + } + + @Override + public int hashCode() { + return modifiers.hashCode() ^ fqName.hashCode() ^ pubApi.hashCode(); + } + + @Override + public String toString() { + return String.format("%s[modifiers: %s, fqName: %s, pubApi: %s]", + getClass().getSimpleName(), + modifiers, + fqName, + pubApi); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubVar.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubVar.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/PubVar.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; +import java.util.Optional; +import java.util.Set; + +import javax.lang.model.element.Modifier; + +public class PubVar implements Serializable { + + private static final long serialVersionUID = 5806536061153374575L; + + public final Set modifiers; + public final TypeDesc type; + public final String identifier; + private final String constValue; + + public PubVar(Set modifiers, + TypeDesc type, + String identifier, + String constValue) { + this.modifiers = modifiers; + this.type = type; + this.identifier = identifier; + this.constValue = constValue; + } + + public String getIdentifier() { + return identifier; + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PubVar other = (PubVar) obj; + return modifiers.equals(other.modifiers) + && type.equals(other.type) + && identifier.equals(other.identifier) + && getConstValue().equals(other.getConstValue()); + } + + @Override + public int hashCode() { + return modifiers.hashCode() + ^ type.hashCode() + ^ identifier.hashCode() + ^ getConstValue().hashCode(); + } + + public String toString() { + return String.format("%s[modifiers: %s, type: %s, identifier: %s, constValue: %s]", + getClass().getSimpleName(), + modifiers, + type, + identifier, + constValue); + } + + public Optional getConstValue() { + return Optional.ofNullable(constValue); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ReferenceTypeDesc.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ReferenceTypeDesc.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/ReferenceTypeDesc.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; + +import javax.lang.model.type.TypeKind; + +public class ReferenceTypeDesc extends TypeDesc implements Serializable { + + private static final long serialVersionUID = 3357616754544796372L; + + // Example: "java.util.Vector" + String javaType; + + public ReferenceTypeDesc(String javaType) { + super(TypeKind.DECLARED); + this.javaType = javaType; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) + return false; + return javaType.equals(((ReferenceTypeDesc) obj).javaType); + } + + @Override + public int hashCode() { + return super.hashCode() ^ javaType.hashCode(); + } + + @Override + public String toString() { + return String.format("%s[type: %s]", getClass().getSimpleName(), javaType); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeDesc.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeDesc.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeDesc.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; +import java.util.Locale; + +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.util.SimpleTypeVisitor9; + +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; + +public abstract class TypeDesc implements Serializable { + + private static final long serialVersionUID = -8201634143915519172L; + + TypeKind typeKind; + + public TypeDesc(TypeKind typeKind) { + this.typeKind = typeKind; + } + + public static TypeDesc decodeString(String s) { + s = s.trim(); + if (s.endsWith("[]")) { + String componentPart = s.substring(0, s.length()-2); + return new ArrayTypeDesc(decodeString(componentPart)); + } + + if (s.startsWith("#")) + return new TypeVarTypeDesc(s.substring(1)); + + if (s.matches("boolean|byte|char|double|float|int|long|short|void")) + return new PrimitiveTypeDesc(TypeKind.valueOf(s.toUpperCase(Locale.US))); + + return new ReferenceTypeDesc(s); + } + + public static String encodeAsString(TypeDesc td) { + if (td.typeKind.isPrimitive() || td.typeKind == TypeKind.VOID) + return td.typeKind.toString().toLowerCase(Locale.US); + + if (td.typeKind == TypeKind.ARRAY) + return encodeAsString(((ArrayTypeDesc) td).compTypeDesc) + "[]"; + + if (td.typeKind == TypeKind.TYPEVAR) + return "#" + ((TypeVarTypeDesc) td).identifier; + + if (td.typeKind == TypeKind.DECLARED) + return ((ReferenceTypeDesc) td).javaType.toString(); + + throw new AssertionError("Unhandled type: " + td.typeKind); + } + + public static TypeDesc fromType(TypeMirror type) { + TypeVisitor v = new SimpleTypeVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitArray(ArrayType t, Void p) { + return new ArrayTypeDesc(t.getComponentType().accept(this, p)); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitDeclared(DeclaredType t, Void p) { + return new ReferenceTypeDesc(t.toString()); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitNoType(NoType t, Void p) { + return new PrimitiveTypeDesc(TypeKind.VOID); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitTypeVariable(TypeVariable t, Void p) { + return new TypeVarTypeDesc(t.toString()); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitPrimitive(PrimitiveType t, Void p) { + return new PrimitiveTypeDesc(t.getKind()); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public TypeDesc visitError(ErrorType t, Void p) { + return new ReferenceTypeDesc(""); + } + }; + + TypeDesc td = v.visit(type); + if (td == null) + throw new AssertionError("Unhandled type mirror: " + type + " (" + type.getClass() + ")"); + return td; + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + return typeKind.equals(((TypeDesc) obj).typeKind); + } + + @Override + public int hashCode() { + return typeKind.hashCode(); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeVarTypeDesc.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeVarTypeDesc.java new file mode 100644 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/pubapi/TypeVarTypeDesc.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014, 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.pubapi; + +import java.io.Serializable; + +import javax.lang.model.type.TypeKind; + +public class TypeVarTypeDesc extends TypeDesc implements Serializable { + + private static final long serialVersionUID = 3357616754544796373L; + + String identifier; // Example: "T" + + public TypeVarTypeDesc(String identifier) { + super(TypeKind.TYPEVAR); + this.identifier = identifier; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) + return false; + return identifier.equals(((TypeVarTypeDesc) obj).identifier); + } + + @Override + public int hashCode() { + return super.hashCode() ^ identifier.hashCode(); + } + + @Override + public String toString() { + return String.format("%s[identifier: %s]", + getClass().getSimpleName(), + identifier); + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/CompilationResult.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/CompilationResult.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/CompilationResult.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/CompilationResult.java @@ -31,6 +31,8 @@ import java.util.Map; import java.util.Set; +import com.sun.tools.sjavac.pubapi.PubApi; + /** * *

This is NOT part of any supported API. @@ -47,8 +49,10 @@ public int returnCode; public Map> packageArtifacts = new HashMap<>(); - public Map> packageDependencies = new HashMap<>(); - public Map packagePubapis = new HashMap<>(); + public Map>> packageDependencies = new HashMap<>(); + public Map>> packageCpDependencies = new HashMap<>(); + public Map packagePubapis = new HashMap<>(); + public Map dependencyPubapis = new HashMap<>(); public String stdout = ""; public String stderr = ""; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/server/ServerMain.java @@ -27,6 +27,8 @@ import java.io.IOException; +import com.sun.tools.sjavac.Log; + /** *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. @@ -36,6 +38,8 @@ public class ServerMain { public static int run(String[] args) { + Log.initializeLog(System.out, System.err); + // Any options other than --startserver? if (args.length > 1) { System.err.println("When spawning a background server, only a single --startserver argument is allowed."); diff --git a/test/tools/sjavac/ClasspathDependencies.java b/test/tools/sjavac/ClasspathDependencies.java new file mode 100644 --- /dev/null +++ b/test/tools/sjavac/ClasspathDependencies.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2014, 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. + */ + +/* + * @test + * @bug 8054717 + * @summary Make sure changes of public API on classpath triggers recompilation + * @library /tools/lib + * @build Wrapper ToolBox + * @run main Wrapper ClasspathDependencies + */ + +import static com.sun.tools.javac.util.Assert.check; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; + +public class ClasspathDependencies extends SjavacBase { + + static final String server = "--server:portfile=testserver,background=false"; + + public static void main(String... args) throws Exception { + + Path root = Paths.get(ClasspathDependencies.class.getSimpleName() + "Test"); + + delete(root); + + Path src = root.resolve("src"); + Path classes = root.resolve("classes"); + Path srcDep = root.resolve("srcDep"); + Path classesDep = root.resolve("classesDep"); + + //////////////////////////////////////////////////////////////////////// + headline("Create a test dependency, Dep.class, and put it in the classpath dir"); + String depCode = "package dep; public class Dep { public void m1() {} }"; + toolbox.writeFile(srcDep.resolve("dep/Dep.java"), depCode); + int rc = compile(server, "-d", classesDep, srcDep); + check(rc == 0, "Compilation failed unexpectedly"); + + //////////////////////////////////////////////////////////////////////// + headline("Compile and link against the Dep.class"); + toolbox.writeFile(src.resolve("pkg/C.java"), + "package pkg;" + + "import dep.Dep;" + + "public class C { public void m() { new Dep().m1(); } }"); + rc = compile(server, "-d", classes, src, "-cp", classesDep); + check(rc == 0, "Compilation failed unexpectedly"); + FileTime modTime1 = Files.getLastModifiedTime(classes.resolve("pkg/C.class")); + + //////////////////////////////////////////////////////////////////////// + headline("Update dependency (without changing the public api)"); + Thread.sleep(1000); + depCode = depCode.replaceAll("}$", "private void m2() {} }"); + toolbox.writeFile(srcDep.resolve("dep/Dep.java"), depCode); + rc = compile(server, "-d", classesDep, srcDep); + check(rc == 0, "Compilation failed unexpectedly"); + + //////////////////////////////////////////////////////////////////////// + headline("Make sure that this does not trigger recompilation of C.java"); + rc = compile(server, "-d", classes, src, "-cp", classesDep); + check(rc == 0, "Compilation failed unexpectedly"); + FileTime modTime2 = Files.getLastModifiedTime(classes.resolve("pkg/C.class")); + check(modTime1.equals(modTime2), "Recompilation erroneously triggered"); + + //////////////////////////////////////////////////////////////////////// + headline("Update public API of dependency"); + Thread.sleep(1000); + depCode = depCode.replace("m1()", "m1(String... arg)"); + toolbox.writeFile(srcDep.resolve("dep/Dep.java"), depCode); + rc = compile(server, "-d", classesDep, srcDep); + check(rc == 0, "Compilation failed unexpectedly"); + + //////////////////////////////////////////////////////////////////////// + headline("Make sure that recompilation of C.java is triggered"); + rc = compile(server, "-d", classes, src, "-cp", classesDep); + check(rc == 0, "Compilation failed unexpectedly"); + FileTime modTime3 = Files.getLastModifiedTime(classes.resolve("pkg/C.class")); + check(modTime2.compareTo(modTime3) < 0, "Recompilation not triggered"); + } + + static void headline(String str) { + System.out.println(); + System.out.println(str); + System.out.println(str.replaceAll(".", "-")); + } + + static void delete(Path root) throws IOException { + if (!Files.exists(root)) + return; + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path f, BasicFileAttributes a) + throws IOException { + Files.delete(f); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) + throws IOException { + if (e != null) + throw e; + if (!dir.equals(root)) + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + +} diff --git a/test/tools/sjavac/DependencyCollection.java b/test/tools/sjavac/DependencyCollection.java --- a/test/tools/sjavac/DependencyCollection.java +++ b/test/tools/sjavac/DependencyCollection.java @@ -32,6 +32,7 @@ * @run main Wrapper DependencyCollection */ +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Path; @@ -39,18 +40,21 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; -import javax.tools.ToolProvider; import com.sun.tools.javac.api.JavacTaskImpl; -import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; import com.sun.tools.sjavac.comp.SmartFileManager; import com.sun.tools.sjavac.comp.dependencies.DependencyCollector; @@ -58,30 +62,35 @@ public static void main(String[] args) throws IOException { Path src = Paths.get(ToolBox.testSrc, "test-input", "src"); - - JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); - try (StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null)) { - SmartFileManager smartFileManager = new SmartFileManager(fileManager); + Context context = new Context(); + JavacTool javac = JavacTool.create(); + try (StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null)) { + SmartFileManager smartFileManager = new SmartFileManager(fm); smartFileManager.setSymbolFileEnabled(false); - Iterable fileObjects = - fileManager.getJavaFileObjectsFromFiles(Arrays.asList(src.resolve("pkg/Test.java").toFile())); - JavacTaskImpl task = (JavacTaskImpl) javac.getTask(new PrintWriter(System.out), - smartFileManager, - null, - Arrays.asList("-d", "classes", - "-sourcepath", src.toAbsolutePath().toString()), - null, - fileObjects); - DependencyCollector depsCollector = new DependencyCollector(); - task.addTaskListener(depsCollector); + List srcList = Arrays.asList(src.resolve("pkg/Test.java").toFile()); + JavacTaskImpl task = (JavacTaskImpl) javac.getTask( + new PrintWriter(System.out), + smartFileManager, + null, + Arrays.asList("-d", "classes", + "-sourcepath", src.toAbsolutePath().toString()), + null, + fm.getJavaFileObjectsFromFiles(srcList), + context); + DependencyCollector dc = new DependencyCollector(context); + task.addTaskListener(dc); task.doCall(); - // Find pkg symbol - PackageSymbol pkg = findPkgSymbolWithName(depsCollector.getSourcePackages(), "pkg"); - Set foundDependencies = depsCollector.getDependenciesForPkg(pkg); + // Find Tset.java compilation unit + JCCompilationUnit testCU = null; + for (JCCompilationUnit cu : dc.collectedDependencies.keySet()) { + if (cu.getSourceFile().getName().endsWith("/Test.java")) + testCU = cu; + } + Assert.check(testCU != null, "Could not find Test.java compilation unit."); + Set foundDependencies = dc.collectedDependencies.get(testCU).get("pkg.Test"); // Print dependencies - System.out.println("Found dependencies:"); foundDependencies.stream() .sorted(Comparator.comparing(DependencyCollection::extractNumber)) .forEach(p -> System.out.println(" " + p)); @@ -92,8 +101,9 @@ .collect(Collectors.toSet()); found.remove(-1); // Dependencies with no number (java.lang etc) Set expected = new HashSet<>(); - for (int i = 2; i <= 30; i++) { - if (i == 15) continue; // Case 15 correspond to the type of a throw-away return value. + for (int i = 2; i <= 32; i++) { + if (i == 15) // Case 15 correspond to the type of a + continue; // throw-away return value. expected.add(i); } @@ -113,18 +123,13 @@ if (missing.size() > 0 || unexpected.size() > 0) throw new AssertionError("Missing and/or unexpected dependencies found."); + + System.out.println("Test passed"); } } - private static PackageSymbol findPkgSymbolWithName(Set syms, String name) { - for (PackageSymbol ps : syms) - if (ps.fullname.toString().equals("pkg")) - return ps; - throw new AssertionError("Could not find package named \"pkg\"."); - } - - public static int extractNumber(PackageSymbol p) { - Matcher m = Pattern.compile("\\d+").matcher(p.fullname.toString()); + public static int extractNumber(Symbol p) { + Matcher m = Pattern.compile("\\d+").matcher(p.name.toString()); if (!m.find()) return -1; return Integer.parseInt(m.group()); diff --git a/test/tools/sjavac/IncCompInheritance.java b/test/tools/sjavac/IncCompInheritance.java --- a/test/tools/sjavac/IncCompInheritance.java +++ b/test/tools/sjavac/IncCompInheritance.java @@ -57,6 +57,7 @@ throw new AssertionError("Compilation failed unexpectedly"); // Remove method A.m + Thread.sleep(1000); // Make sure we get a new timestamp String aModified = "package pkga; public class A { }"; toolbox.writeFile(src.resolve("pkga/A.java"), aModified); diff --git a/test/tools/sjavac/JavacOptionPrep.java b/test/tools/sjavac/JavacOptionPrep.java --- a/test/tools/sjavac/JavacOptionPrep.java +++ b/test/tools/sjavac/JavacOptionPrep.java @@ -82,6 +82,7 @@ // Check the result boolean destDirFound = false; + boolean userPathsFirst = false; boolean headerDirFound = false; boolean gensrcDirFound = false; boolean classPathFound = false; @@ -165,7 +166,6 @@ if (!implicitNoneFound) throw new AssertionError("\"-implicit:none\" not found."); - } static void assertEquals(Object expected, Object actual) { diff --git a/test/tools/sjavac/SjavacBase.java b/test/tools/sjavac/SjavacBase.java --- a/test/tools/sjavac/SjavacBase.java +++ b/test/tools/sjavac/SjavacBase.java @@ -37,7 +37,7 @@ */ public static int compile(Object... args) throws ReflectiveOperationException { // Use reflection to avoid a compile-time dependency on sjavac Main - System.err.println("compile: " + Arrays.toString(args)); + System.out.println("compile: " + Arrays.toString(args)); Class c = Class.forName("com.sun.tools.sjavac.Main"); Method m = c.getDeclaredMethod("go", String[].class); String[] strArgs = new String[args.length]; diff --git a/test/tools/sjavac/test-input/src/pkg/Test.java b/test/tools/sjavac/test-input/src/pkg/Test.java --- a/test/tools/sjavac/test-input/src/pkg/Test.java +++ b/test/tools/sjavac/test-input/src/pkg/Test.java @@ -30,9 +30,10 @@ import pkg3.Cls3; // pkg3.Cls3 import pkg25.Cls25; // pkg25.Cls25 import nondependency.pkg26.Cls26; // pkg26.Cls26 (but not nondependency) -import pkg28.Cls28.Inner28; // pkg29.Cls28, pkg29.Cls28.Inner28 +import pkg28.Cls28.Inner28; // pkg28.Cls28, pkg28.Cls28.Inner28 import static pkg29.Cls29.Inner29; // pkg29.Cls29, pkg29.Cls29.Inner29 import static pkg30.Cls30.*; // pkg30.Cls30 as a whole +import static pkg31.Cls31.Inner31.InnerInner31.*; @pkg5.Anno5 // pkg5.Anno5 public class Test // pkg23.Cls23 @@ -55,7 +56,7 @@ return null; } - public void m2() { // pkg18.Cls18 + public void m2() { // pkg18.Cls18, pkg32.Cls32 } public T m3() { diff --git a/test/tools/sjavac/test-input/src/pkg31/Cls31.java b/test/tools/sjavac/test-input/src/pkg31/Cls31.java new file mode 100644 --- /dev/null +++ b/test/tools/sjavac/test-input/src/pkg31/Cls31.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, 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 pkg31; + +public class Cls31 { + public static class Inner31 { + public static class InnerInner31 { + public static int I = 1337; + } + } +} diff --git a/test/tools/sjavac/test-input/src/pkg32/Cls32.java b/test/tools/sjavac/test-input/src/pkg32/Cls32.java new file mode 100644 --- /dev/null +++ b/test/tools/sjavac/test-input/src/pkg32/Cls32.java @@ -0,0 +1,4 @@ +package pkg32; + +public interface Cls32 { +}