--- old/common/bin/compare_exceptions.sh.incl 2012-12-06 15:26:04.286431195 +0100 +++ new/common/bin/compare_exceptions.sh.incl 2012-12-06 15:26:04.166410495 +0100 @@ -80,6 +80,7 @@ ./bin/javadoc ./bin/javah ./bin/javap +./bin/jdeps ./bin/jcmd ./bin/jconsole ./bin/jdb @@ -167,6 +168,7 @@ ./bin/javadoc ./bin/javah ./bin/javap +./bin/jdeps ./bin/jcmd ./bin/jconsole ./bin/jdb @@ -309,6 +311,7 @@ ./bin/javadoc ./bin/javah ./bin/javap +./bin/jdeps ./bin/javaws ./bin/jcmd ./bin/jconsole @@ -449,6 +452,7 @@ ./bin/amd64/javadoc ./bin/amd64/javah ./bin/amd64/javap +./bin/amd64/jdeps ./bin/amd64/jcmd ./bin/amd64/jconsole ./bin/amd64/jdb @@ -606,6 +610,7 @@ ./bin/javadoc ./bin/javah ./bin/javap +./bin/jdeps ./bin/javaws ./bin/jcmd ./bin/jconsole @@ -751,6 +756,7 @@ ./bin/sparcv9/javadoc ./bin/sparcv9/javah ./bin/sparcv9/javap +./bin/sparcv9/jdeps ./bin/sparcv9/jcmd ./bin/sparcv9/jconsole ./bin/sparcv9/jdb @@ -826,6 +832,7 @@ ./bin/javadoc.exe ./bin/javah.exe ./bin/javap.exe +./bin/jdeps.exe ./bin/javaw.exe ./bin/jcmd.exe ./bin/jconsole.exe @@ -910,6 +917,7 @@ ./bin/javadoc ./bin/javah ./bin/javap +./bin/jdeps ./bin/jcmd ./bin/jconsole ./bin/jdb --- old/jdk/make/common/Release.gmk 2012-12-06 15:26:04.986551933 +0100 +++ new/jdk/make/common/Release.gmk 2012-12-06 15:26:04.636491564 +0100 @@ -374,6 +374,7 @@ com/sun/tools/javadoc \ com/sun/tools/javah \ com/sun/tools/javap \ + com/sun/tools/jdeps \ com/sun/tools/corba \ com/sun/tools/internal/xjc \ com/sun/tools/internal/ws \ @@ -456,6 +457,7 @@ javadoc$(EXE_SUFFIX) \ javah$(EXE_SUFFIX) \ javap$(EXE_SUFFIX) \ + jdeps$(EXE_SUFFIX) \ jcmd$(EXE_SUFFIX) \ jdb$(EXE_SUFFIX) \ jps$(EXE_SUFFIX) \ @@ -563,6 +565,7 @@ $(ECHO) "sun/tools/javac/" >> $@ $(ECHO) "com/sun/tools/classfile/" >> $@ $(ECHO) "com/sun/tools/javap/" >> $@ + $(ECHO) "com/sun/tools/jdeps/" >> $@ $(ECHO) "sun/tools/jcmd/" >> $@ $(ECHO) "sun/tools/jconsole/" >> $@ $(ECHO) "sun/tools/jps/" >> $@ --- old/jdk/make/launchers/Makefile 2012-12-06 15:26:05.686672678 +0100 +++ new/jdk/make/launchers/Makefile 2012-12-06 15:26:05.416626100 +0100 @@ -63,6 +63,7 @@ $(call make-launcher, javadoc, com.sun.tools.javadoc.Main, , ) $(call make-launcher, javah, com.sun.tools.javah.Main, , ) $(call make-launcher, javap, com.sun.tools.javap.Main, , ) +$(call make-launcher, jdeps, com.sun.tools.jdeps.Main, , ) $(call make-launcher, jcmd, sun.tools.jcmd.JCmd, , ) $(call make-launcher, jconsole, sun.tools.jconsole.JConsole, \ -J-Djconsole.showOutputViewer, ) --- old/jdk/make/launchers/Makefile.launcher 2012-12-06 15:26:06.386793417 +0100 +++ new/jdk/make/launchers/Makefile.launcher 2012-12-06 15:26:06.106745123 +0100 @@ -62,6 +62,10 @@ WILDCARDS=true NEVER_ACT_AS_SERVER_CLASS_MACHINE=true endif +ifeq ($(PROGRAM),jdeps) + WILDCARDS=true + NEVER_ACT_AS_SERVER_CLASS_MACHINE=true +endif ifeq ($(PROGRAM),javah) WILDCARDS=true NEVER_ACT_AS_SERVER_CLASS_MACHINE=true --- old/jdk/makefiles/CompileLaunchers.gmk 2012-12-06 15:26:07.286948656 +0100 +++ new/jdk/makefiles/CompileLaunchers.gmk 2012-12-06 15:26:06.936888285 +0100 @@ -267,6 +267,11 @@ -DNEVER_ACT_AS_SERVER_CLASS_MACHINE \ -DJAVA_ARGS='{ "-J-ms8m"$(COMMA) "com.sun.tools.javap.Main"$(COMMA) }')) +$(eval $(call SetupLauncher,jdeps,\ + -DEXPAND_CLASSPATH_WILDCARDS \ + -DNEVER_ACT_AS_SERVER_CLASS_MACHINE \ + -DJAVA_ARGS='{ "-J-ms8m"$(COMMA) "com.sun.tools.jdeps.Main"$(COMMA) }')) + BUILD_LAUNCHER_jconsole_CFLAGS_windows:=-DJAVAW BUILD_LAUNCHER_jconsole_LDFLAGS_windows:=user32.lib --- old/jdk/makefiles/CreateJars.gmk 2012-12-06 15:26:08.087086655 +0100 +++ new/jdk/makefiles/CreateJars.gmk 2012-12-06 15:26:07.827041801 +0100 @@ -738,6 +738,7 @@ com/sun/tools/javadoc \ com/sun/tools/javah \ com/sun/tools/javap \ + com/sun/tools/jdeps \ com/sun/tools/corba \ com/sun/tools/internal/xjc \ com/sun/tools/internal/ws \ --- old/jdk/makefiles/Images.gmk 2012-12-06 15:26:08.797209117 +0100 +++ new/jdk/makefiles/Images.gmk 2012-12-06 15:26:08.537164270 +0100 @@ -100,6 +100,7 @@ javadoc$(EXE_SUFFIX) \ javah$(EXE_SUFFIX) \ javap$(EXE_SUFFIX) \ + jdeps$(EXE_SUFFIX) \ jcmd$(EXE_SUFFIX) \ jdb$(EXE_SUFFIX) \ jps$(EXE_SUFFIX) \ --- old/langtools/make/build.properties 2012-12-06 15:26:09.377309164 +0100 +++ new/langtools/make/build.properties 2012-12-06 15:26:09.187276387 +0100 @@ -152,6 +152,7 @@ javap.includes = \ com/sun/tools/classfile/ \ com/sun/tools/javap/ \ + com/sun/tools/jdeps/ \ sun/tools/javap/ javap.tests = \ --- old/langtools/makefiles/BuildLangtools.gmk 2012-12-06 15:26:09.827386780 +0100 +++ new/langtools/makefiles/BuildLangtools.gmk 2012-12-06 15:26:09.697364355 +0100 @@ -75,6 +75,7 @@ printf "jdk=$(JDK_VERSION)\nfull=$(FULL_VERSION)\nrelease=$(RELEASE)\n" > $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javah/resources/version.properties printf "jdk=$(JDK_VERSION)\nfull=$(FULL_VERSION)\nrelease=$(RELEASE)\n" > $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javap/resources/version.properties printf "jdk=$(JDK_VERSION)\nfull=$(FULL_VERSION)\nrelease=$(RELEASE)\n" > $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javac/resources/version.properties + printf "jdk=$(JDK_VERSION)\nfull=$(FULL_VERSION)\nrelease=$(RELEASE)\n" > $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/jdeps/resources/version.properties echo Compiling $(words $(PROPSOURCES) v1 v2 v3) properties into resource bundles $(TOOL_COMPILEPROPS_CMD) $(PROPCMDLINE) \ -compile $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javah/resources/version.properties \ @@ -85,6 +86,9 @@ java.util.ListResourceBundle \ -compile $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javac/resources/version.properties \ $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/javac/resources/version.java \ + java.util.ListResourceBundle \ + -compile $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/jdeps/resources/version.properties \ + $(LANGTOOLS_OUTPUTDIR)/gensrc/com/sun/tools/jdeps/resources/version.java \ java.util.ListResourceBundle echo PROPS_ARE_CREATED=yes > $@ --- old/langtools/src/share/classes/com/sun/tools/classfile/Dependencies.java 2012-12-06 15:26:10.287466122 +0100 +++ new/langtools/src/share/classes/com/sun/tools/classfile/Dependencies.java 2012-12-06 15:26:10.147441979 +0100 @@ -142,6 +142,15 @@ } /** + * Get a finder to do class dependency analysis. + * + * @return a Class dependency finder + */ + public static Finder getClassDependencyFinder() { + return new ClassDependencyFinder(); + } + + /** * Get the finder used to locate the dependencies for a class. * @return the finder */ @@ -246,8 +255,6 @@ return results; } - - /** * Find the dependencies of a class, using the current * {@link Dependencies#getFinder finder} and @@ -306,38 +313,44 @@ * A location identifying a class. */ static class SimpleLocation implements Location { - public SimpleLocation(String className) { - this.className = className; + public SimpleLocation(String name) { + this.name = name; + this.className = name.replace('/', '.'); + } + + public String getName() { + return name; } - /** - * Get the name of the class being depended on. This name will be used to - * locate the class file for transitive dependency analysis. - * @return the name of the class being depended on - */ public String getClassName() { return className; } + public String getPackageName() { + int i = className.lastIndexOf('.'); + return (i > 0) ? className.substring(0, i) : ""; + } + @Override public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof SimpleLocation)) return false; - return (className.equals(((SimpleLocation) other).className)); + return (name.equals(((SimpleLocation) other).name)); } @Override public int hashCode() { - return className.hashCode(); + return name.hashCode(); } @Override public String toString() { - return className; + return name; } + private String name; private String className; } @@ -431,9 +444,7 @@ } public boolean accepts(Dependency dependency) { - String cn = dependency.getTarget().getClassName(); - int lastSep = cn.lastIndexOf("/"); - String pn = (lastSep == -1 ? "" : cn.substring(0, lastSep)); + String pn = dependency.getTarget().getPackageName(); if (packageNames.contains(pn)) return true; @@ -451,8 +462,6 @@ private final boolean matchSubpackages; } - - /** * This class identifies class names directly or indirectly in the constant pool. */ @@ -462,6 +471,26 @@ for (CPInfo cpInfo: classfile.constant_pool.entries()) { v.scan(cpInfo); } + try { + v.addClass(classfile.super_class); + v.addClasses(classfile.interfaces); + v.scan(classfile.attributes); + + for (Field f : classfile.fields) { + v.scan(f.descriptor, f.attributes); + } + for (Method m : classfile.methods) { + v.scan(m.descriptor, m.attributes); + Exceptions_attribute e = + (Exceptions_attribute)m.attributes.get(Attribute.Exceptions); + if (e != null) { + v.addClasses(e.exception_index_table); + } + } + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + return v.deps; } } @@ -558,9 +587,7 @@ void scan(Descriptor d, Attributes attrs) { try { scan(new Signature(d.index).getType(constant_pool)); - Signature_attribute sa = (Signature_attribute) attrs.get(Attribute.Signature); - if (sa != null) - scan(new Signature(sa.signature_index).getType(constant_pool)); + scan(attrs); } catch (ConstantPoolException e) { throw new ClassFileError(e); } @@ -574,6 +601,43 @@ t.accept(this, null); } + void scan(Attributes attrs) { + try { + Signature_attribute sa = (Signature_attribute)attrs.get(Attribute.Signature); + if (sa != null) + scan(sa.getParsedSignature().getType(constant_pool)); + + scan((RuntimeVisibleAnnotations_attribute) + attrs.get(Attribute.RuntimeVisibleAnnotations)); + scan((RuntimeVisibleParameterAnnotations_attribute) + attrs.get(Attribute.RuntimeVisibleParameterAnnotations)); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + } + + private void scan(RuntimeAnnotations_attribute attr) throws ConstantPoolException { + if (attr == null) { + return; + } + for (int i = 0; i < attr.annotations.length; i++) { + int index = attr.annotations[i].type_index; + scan(new Signature(index).getType(constant_pool)); + } + } + + private void scan(RuntimeParameterAnnotations_attribute attr) throws ConstantPoolException { + if (attr == null) { + return; + } + for (int param = 0; param < attr.parameter_annotations.length; param++) { + for (int i = 0; i < attr.parameter_annotations[param].length; i++) { + int index = attr.parameter_annotations[param][i].type_index; + scan(new Signature(index).getType(constant_pool)); + } + } + } + void addClass(int index) throws ConstantPoolException { if (index != 0) { String name = constant_pool.getClassInfo(index).getBaseName(); @@ -698,6 +762,7 @@ findDependencies(type.paramTypes); findDependencies(type.returnType); findDependencies(type.throwsTypes); + findDependencies(type.typeParamTypes); return null; } @@ -709,7 +774,7 @@ public Void visitClassType(ClassType type, Void p) { findDependencies(type.outerType); - addDependency(type.name); + addDependency(type.getBinaryName()); findDependencies(type.typeArgs); return null; } --- old/langtools/src/share/classes/com/sun/tools/classfile/Dependency.java 2012-12-06 15:26:10.757547197 +0100 +++ new/langtools/src/share/classes/com/sun/tools/classfile/Dependency.java 2012-12-06 15:26:10.637526497 +0100 @@ -71,7 +71,19 @@ * dependency analysis. * @return the name of the class containing the location. */ + String getName(); + + /** + * Get the fully-qualified name of the class containing the location. + * @return the fully-qualified name of the class containing the location. + */ String getClassName(); + + /** + * Get the package name of the class containing the location. + * @return the package name of the class containing the location. + */ + String getPackageName(); } --- old/langtools/test/Makefile 2012-12-06 15:26:11.217626543 +0100 +++ new/langtools/test/Makefile 2012-12-06 15:26:11.087604117 +0100 @@ -229,7 +229,7 @@ all: $(JPRT_CLEAN) jtreg-tests jck-compiler-tests jck-runtime-tests $(JPRT_ARCHIVE_BUNDLE) all-summary @echo "Testing completed successfully" -jtreg apt javac javadoc javah javap: $(JPRT_CLEAN) jtreg-tests $(JPRT_ARCHIVE_BUNDLE) jtreg-summary +jtreg apt javac javadoc javah javap jdeps: $(JPRT_CLEAN) jtreg-tests $(JPRT_ARCHIVE_BUNDLE) jtreg-summary @echo "Testing completed successfully" jck-compiler: $(JPRT_CLEAN) jck-compiler-tests $(JPRT_ARCHIVE_BUNDLE) jck-compiler-summary @@ -246,6 +246,7 @@ javadoc: JTREG_TESTDIRS = tools/javadoc com/sun/javadoc javah: JTREG_TESTDIRS = tools/javah javap: JTREG_TESTDIRS = tools/javap +jdeps: JTREG_TESTDIRS = tools/jdeps # Run jtreg tests # @@ -426,7 +427,7 @@ # Phony targets (e.g. these are not filenames) .PHONY: all clean \ - jtreg javac javadoc javah javap jtreg-tests jtreg-summary check-jtreg \ + jtreg javac javadoc javah javap jdeps jtreg-tests jtreg-summary check-jtreg \ jck-compiler jck-compiler-tests jck-compiler-summary \ jck-runtime jck-runtime-tests jck-runtime-summary check-jck --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java 2012-12-06 15:26:11.627697264 +0100 @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012, 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.jdeps; + +import com.sun.tools.classfile.Dependency; +import com.sun.tools.classfile.Dependency.Location; +import java.io.File; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Represents the source of the class files. + */ +public class Archive { + private static Map archiveForClass = new HashMap<>(); + public static Archive find(String classname) { + return archiveForClass.get(classname); + } + + private final File file; + private final String filename; + private final DependencyRecorder recorder; + private final ClassFileReader reader; + public Archive(File f, ClassFileReader reader) { + this.file = f; + this.filename = f != null ? f.getName() : "not found"; + this.recorder = new DependencyRecorder(); + this.reader = reader; + } + + public ClassFileReader reader() { + return reader; + } + + public String getFileName() { + return filename; + } + + public void addClass(String cn) { + Archive a = archiveForClass.get(cn); + assert(a != null && a != this); // ## issue warning? + if (!archiveForClass.containsKey(cn)) { + archiveForClass.put(cn, this); + } + } + + public void addDependency(Dependency d) { + recorder.addDependency(d); + } + + public SortedMap> getDependencies() { + DependencyRecorder.Filter filter = new DependencyRecorder.Filter() { + public boolean accept(Location origin, Location target) { + String o = origin.getClassName(); + String t = target.getClassName(); + return archiveForClass.get(o) != archiveForClass.get(t); + }}; + + SortedMap> result = new TreeMap<>(locationComparator); + for (Map.Entry> e : recorder.dependencies().entrySet()) { + Location o = e.getKey(); + for (Location t : e.getValue()) { + if (filter.accept(o, t)) { + SortedSet odeps = result.get(o); + if (odeps == null) { + result.put(o, odeps = new TreeSet<>(locationComparator)); + } + odeps.add(t); + } + } + } + return result; + } + + public Set getRequiredArchives() { + SortedSet deps = new TreeSet<>(new Comparator() { + public int compare(Archive a1, Archive a2) { + return a1.toString().compareTo(a2.toString()); + } + }); + + for (Map.Entry> e : recorder.dependencies().entrySet()) { + Location o = e.getKey(); + Archive origin = Archive.find(o.getClassName()); + for (Location t : e.getValue()) { + Archive target = Archive.find(t.getClassName()); + if (origin != target) { + if (!deps.contains(target)) { + deps.add(target); + } + } + } + } + return deps; + } + + public String toString() { + return file != null ? file.getPath() : "not found"; + } + + private static class DependencyRecorder { + static interface Filter { + boolean accept(Location origin, Location target); + } + + public void addDependency(Dependency d) { + Set odeps = map.get(d.getOrigin()); + if (odeps == null) { + map.put(d.getOrigin(), odeps = new HashSet<>()); + } + odeps.add(d.getTarget()); + } + + public Map> dependencies() { + return map; + } + + private final Map> map = new HashMap<>(); + } + + private static Comparator locationComparator = + new Comparator() { + public int compare(Location o1, Location o2) { + return o1.toString().compareTo(o2.toString()); + } + }; +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java 2012-12-06 15:26:12.057771440 +0100 @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2012, 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.jdeps; + +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.Dependencies.ClassFileError; +import java.io.*; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * ClassFileReader reads ClassFile(s) of a given path that can be + * a .class file, a directory, or a JAR file. + */ +public class ClassFileReader { + /** + * Returns a ClassFileReader instance of a given path. + */ + public static ClassFileReader newInstance(File path) throws IOException { + if (!path.exists()) { + throw new FileNotFoundException(path.getAbsolutePath()); + } + + if (path.isDirectory()) { + return new DirectoryReader(path.toPath()); + } else if (path.getName().endsWith(".jar")) { + return new JarFileReader(path.toPath()); + } else { + return new ClassFileReader(path.toPath()); + } + } + + protected final Path path; + protected final String baseFileName; + private ClassFileReader(Path path) { + this.path = path; + this.baseFileName = path.getFileName().toString(); + } + + public String getFileName() { + return path.getFileName().toString(); + } + + public ClassFile getClassFile(String classname) { + String fn = classname.replace('.', File.separatorChar) + ".class"; + if (!fn.equals(baseFileName)) { + return null; + } + + try (InputStream is = Files.newInputStream(path)) { + return ClassFile.read(is); + } catch (IOException | ConstantPoolException e) { + throw new ClassFileError(e); + } + } + + public Iterable getClassFiles() throws IOException { + return new Iterable() { + public Iterator iterator() { + return new FileIterator(); + } + }; + } + + class FileIterator implements Iterator { + int count; + FileIterator() { + this.count = 0; + } + public boolean hasNext() { + return count == 0 && baseFileName.endsWith(".class"); + } + + public ClassFile next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + try (InputStream is = Files.newInputStream(path)) { + count++; + return ClassFile.read(is); + } catch (IOException | ConstantPoolException e) { + throw new ClassFileError(e); + } + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + public String toString() { + return path.toString(); + } + + private static class DirectoryReader extends ClassFileReader { + DirectoryReader(Path path) throws IOException { + super(path); + } + + @Override + public ClassFile getClassFile(String classname) { + String fn = classname.replace('.', '/') + ".class"; + Path p = path.resolve(fn); + if (!p.toFile().exists()) { + return null; + } + try (InputStream is = Files.newInputStream(p)) { + return ClassFile.read(is); + } catch (IOException | ConstantPoolException e) { + throw new ClassFileError(e); + } + } + + public Iterable getClassFiles() throws IOException { + final Iterator iter = new DirectoryIterator(); + return new Iterable() { + public Iterator iterator() { + return iter; + } + }; + } + + private List walkTree(Path dir) throws IOException { + final List files = new ArrayList<>(); + Files.walkFileTree(dir, new SimpleFileVisitor() { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (file.toFile().getName().endsWith(".class")) { + files.add(file); + } + return FileVisitResult.CONTINUE; + } + }); + return files; + } + + class DirectoryIterator implements Iterator { + private List entries; + private int index = 0; + DirectoryIterator() throws IOException { + entries = walkTree(path); + index = 0; + } + + public boolean hasNext() { + return index != entries.size(); + } + + public ClassFile next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Path path = entries.get(index++); + try (InputStream is = Files.newInputStream(path)) { + return ClassFile.read(is); + } catch (IOException | ConstantPoolException e) { + throw new ClassFileError(e); + } + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + } + + private static class JarFileReader extends ClassFileReader { + final JarFile jarfile; + JarFileReader(Path path) throws IOException { + super(path); + this.jarfile = new JarFile(path.toFile()); + } + + public ClassFile getClassFile(String classname) { + String fn = classname.replace('.', '/') + ".class"; + JarEntry e = jarfile.getJarEntry(fn); + if (e == null) { + return null; + } + + try (InputStream is = jarfile.getInputStream(e)) { + return ClassFile.read(is); + } catch (IOException | ConstantPoolException ex) { + throw new ClassFileError(ex); + } + } + + public Iterable getClassFiles() throws IOException { + final Iterator iter = new JarFileIterator(); + return new Iterable() { + public Iterator iterator() { + return iter; + } + }; + } + + class JarFileIterator implements Iterator { + private Enumeration entries; + private JarEntry nextEntry; + JarFileIterator() { + this.entries = jarfile.entries(); + while (entries.hasMoreElements()) { + JarEntry e = entries.nextElement(); + String name = e.getName(); + if (name.endsWith(".class")) { + this.nextEntry = e; + break; + } + } + } + + public boolean hasNext() { + return nextEntry != null; + } + + public ClassFile next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + ClassFile cf; + try { + cf = ClassFile.read(jarfile.getInputStream(nextEntry)); + } catch (IOException | ConstantPoolException ex) { + throw new ClassFileError(ex); + } + JarEntry entry = nextEntry; + nextEntry = null; + while (entries.hasMoreElements()) { + JarEntry e = entries.nextElement(); + String name = e.getName(); + if (name.endsWith(".class")) { + nextEntry = e; + break; + } + } + return cf; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + } +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java 2012-12-06 15:26:12.487845601 +0100 @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2012, 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.jdeps; + +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.Dependencies; +import com.sun.tools.classfile.Dependencies.ClassFileError; +import com.sun.tools.classfile.Dependency; +import com.sun.tools.classfile.Dependency.Location; +import java.io.*; +import java.text.MessageFormat; +import java.util.*; +import java.util.regex.Pattern; + +/** + * Implementation for the jdeps tool for static class dependency analysis. + */ +class JdepsTask { + class BadArgs extends Exception { + static final long serialVersionUID = 8765093759964640721L; + BadArgs(String key, Object... args) { + super(JdepsTask.this.getMessage(key, args)); + this.key = key; + this.args = args; + } + + BadArgs showUsage(boolean b) { + showUsage = b; + return this; + } + final String key; + final Object[] args; + boolean showUsage; + } + + static abstract class Option { + Option(boolean hasArg, String... aliases) { + this.hasArg = hasArg; + this.aliases = aliases; + } + + boolean isHidden() { + return false; + } + + boolean matches(String opt) { + for (String a : aliases) { + if (a.equals(opt)) { + return true; + } + } + return false; + } + + boolean ignoreRest() { + return false; + } + + abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; + final boolean hasArg; + final String[] aliases; + } + + static Option[] recognizedOptions = { + new Option(false, "-h", "--help") { + void process(JdepsTask task, String opt, String arg) { + task.options.help = true; + } + }, + new Option(false, "-version") { + void process(JdepsTask task, String opt, String arg) { + task.options.version = true; + } + }, + new Option(false, "-fullversion") { + void process(JdepsTask task, String opt, String arg) { + task.options.fullVersion = true; + } + boolean isHidden() { + return true; + } + }, + new Option(true, "-classpath") { + void process(JdepsTask task, String opt, String arg) { + task.options.classpath = arg; + } + }, + new Option(false, "-summary") { + void process(JdepsTask task, String opt, String arg) { + task.options.showSummary = true; + task.options.verbose = Options.Verbose.SUMMARY; + } + }, + new Option(false, "-v:class") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.CLASS; + } + }, + new Option(false, "-v:package") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.PACKAGE; + } + }, + new Option(true, "-p", "--package") { + void process(JdepsTask task, String opt, String arg) { + task.options.packageNames.add(arg); + } + }, + new Option(true, "-e", "--regex") { + void process(JdepsTask task, String opt, String arg) { + task.options.regex = arg; + } + }, + new Option(false, "-P", "--profile") { + void process(JdepsTask task, String opt, String arg) { + task.options.showProfile = true; + } + }, + new Option(false, "-R", "--recursive") { + void process(JdepsTask task, String opt, String arg) { + task.options.depth = 0; + } + }, + new Option(true, "-d", "--depth") { + void process(JdepsTask task, String opt, String arg) throws BadArgs { + try { + task.options.depth = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw task.new BadArgs("err.invalid.arg.for.option", opt); + } + } + boolean isHidden() { + return true; + } + }, + new Option(false, "-all") { + void process(JdepsTask task, String opt, String arg) { + task.options.all = true; + } + }}; + + private static final String PROGNAME = "jdeps"; + private final Options options = new Options(); + private final List filenames = new ArrayList<>(); + + private PrintWriter log; + void setLog(PrintWriter out) { + log = out; + } + + /** + * Result codes. + */ + static final int EXIT_OK = 0, // Completed with no errors. + EXIT_ERROR = 1, // Completed but reported errors. + EXIT_CMDERR = 2, // Bad command-line arguments + EXIT_SYSERR = 3, // System error or resource exhaustion. + EXIT_ABNORMAL = 4;// terminated abnormally + + int run(String[] args) { + if (log == null) { + log = new PrintWriter(System.out); + } + try { + handleOptions(Arrays.asList(args)); + if (filenames.isEmpty() && (!options.all || options.classpath.isEmpty())) { + if (options.help || options.version || options.fullVersion) { + return EXIT_OK; + } else { + return EXIT_CMDERR; + } + } + if (options.regex != null && options.packageNames.size() > 0) { + showHelp(); + return EXIT_CMDERR; + } + if (options.showSummary && options.verbose != Options.Verbose.SUMMARY) { + showHelp(); + return EXIT_CMDERR; + } + boolean ok = run(); + return ok ? EXIT_OK : EXIT_ERROR; + } catch (BadArgs e) { + reportError(e.key, e.args); + if (e.showUsage) { + log.println(getMessage("main.usage.summary", PROGNAME)); + } + return EXIT_CMDERR; + } catch (IOException e) { + return EXIT_ABNORMAL; + } finally { + log.flush(); + } + } + + private boolean run() throws IOException { + Dependency.Finder finder = Dependencies.getClassDependencyFinder(); + Dependency.Filter filter; + if (options.regex != null) { + filter = Dependencies.getRegexFilter(Pattern.compile(options.regex)); + } else if (options.packageNames.size() > 0) { + filter = Dependencies.getPackageFilter(options.packageNames, false); + } else { + filter = new Dependency.Filter() { + public boolean accepts(Dependency dependency) { + return !dependency.getOrigin().equals(dependency.getTarget()); + } + }; + } + + findDependencies(finder, filter); + switch (options.verbose) { + case CLASS: + printClassDeps(log); + break; + case PACKAGE: + printPackageDeps(log); + break; + case SUMMARY: + for (Archive origin : sourceLocations) { + for (Archive target : origin.getRequiredArchives()) { + log.format("%-30s -> %s%n", origin, target); + } + } + break; + default: + throw new InternalError("Should not reach here"); + } + return true; + } + + private final List sourceLocations = new ArrayList<>(); + private final Archive NOT_FOUND = new Archive(null, null); + private void findDependencies(Dependency.Finder finder, + Dependency.Filter filter) + throws IOException + { + // Work queue of names of classfiles to be searched. + // Entries will be unique, and for classes that do not yet have + // dependencies in the results map. + Deque deque = new LinkedList<>(); + Set doneClasses = new HashSet<>(); + + List archives = new ArrayList<>(getArchives(filenames)); + List classpaths = classPathArchives(options.classpath); + List platformClassPath = PlatformClassPath.getArchives(); + sourceLocations.addAll(archives); + sourceLocations.addAll(classpaths); + sourceLocations.addAll(platformClassPath); + if (options.all) { + archives.addAll(classpaths); + } + + // get the immediate dependencies of the input files + for (Archive a : archives) { + for (ClassFile cf : a.reader().getClassFiles()) { + String className; + try { + className = cf.getName().replace('/', '.'); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + a.addClass(className); + if (!doneClasses.contains(className)) { + doneClasses.add(className); + } + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getClassName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addDependency(d); + } + } + } + } + + // add Archive for looking up classes from the classpath + // for transitive dependency analysis + int depths = options.depth == 0 ? Integer.MAX_VALUE : options.depth; + List cpathReaders = new ArrayList<>(); + if (!options.all) { + cpathReaders.addAll(classpaths); + } + cpathReaders.addAll(platformClassPath); + while (!deque.isEmpty() && depths-- >= 0) { + Deque unresolved = deque; + deque = new LinkedList<>(); + String className; + while ((className = unresolved.poll()) != null) { + if (doneClasses.contains(className)) { + continue; + } + + ClassFile cf = null; + for (Archive a : cpathReaders) { + cf = a.reader().getClassFile(className); + if (cf != null) { + a.addClass(className); + doneClasses.add(className); + if (depths > 0) { + // do dependency analysis if depths > 0 + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getClassName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addDependency(d); + } + } + } + break; + } + } + + if (cf == null) { + NOT_FOUND.addClass(className); + } + } + } + } + + private void printPackageDeps(PrintWriter out) { + for (Archive source : sourceLocations) { + SortedMap> deps = source.getDependencies(); + if (deps.isEmpty()) + continue; + + for (Archive target : source.getRequiredArchives()) { + out.format("%s -> %s%n", source, target); + } + + Map pkgs = new TreeMap<>(); + SortedMap targets = new TreeMap<>(); + String pkg = ""; + for (Map.Entry> e : deps.entrySet()) { + Location o = e.getKey(); + String p = packageOf(o); + Archive origin = Archive.find(o.getClassName()); + if (!pkgs.containsKey(p)) { + pkgs.put(p, origin); + } else if (pkgs.get(p) != origin) { + warning("warn.split.package", p, origin, pkgs.get(p)); + } + + if (!p.equals(pkg)) { + printTargets(out, targets); + pkg = p; + targets.clear(); + out.format(" %s (%s)%n", p, Archive.find(o.getClassName()).getFileName()); + } + + for (Location t : e.getValue()) { + p = packageOf(t); + Archive target = Archive.find(t.getClassName()); + if (!targets.containsKey(p)) { + targets.put(p, target); + } + } + } + printTargets(out, targets); + out.println(); + } + } + + private void printTargets(PrintWriter out, Map targets) { + for (Map.Entry t : targets.entrySet()) { + String pn = t.getKey(); + out.format(" -> %-40s %s%n", pn, getPackageInfo(pn, t.getValue())); + } + } + + private String getPackageInfo(String pn, Archive source) { + if (PlatformClassPath.contains(source)) { + String name = PlatformClassPath.getProfileName(pn); + if (name.isEmpty()) { + return "JDK internal API (" + source.getFileName() + ")"; + } + return options.showProfile ? name : ""; + } + return source.getFileName(); + } + + private static String packageOf(Location loc) { + String cn = loc.getClassName(); + int i = cn.lastIndexOf('.'); + return i > 0 ? cn.substring(0, i) : ""; + } + + private void printClassDeps(PrintWriter out) { + for (Archive source : sourceLocations) { + SortedMap> deps = source.getDependencies(); + if (deps.isEmpty()) + continue; + + for (Archive target : source.getRequiredArchives()) { + out.format("%s -> %s%n", source, target); + } + out.format("%s%n", source); + for (Map.Entry> e : deps.entrySet()) { + Location o = e.getKey(); + String cn = o.getClassName(); + Archive origin = Archive.find(cn); + out.format(" %s (%s)%n", cn, origin.getFileName()); + for (Location t : e.getValue()) { + cn = t.getClassName(); + Archive target = Archive.find(cn); + out.format(" -> %-60s %s%n", cn, getPackageInfo(packageOf(t), target)); + } + } + out.println(); + } + } + + public void handleOptions(Iterable args) throws BadArgs { + Iterator iter = args.iterator(); + boolean noArgs = !iter.hasNext(); + while (iter.hasNext()) { + String arg = iter.next(); + if (arg.startsWith("-")) { + handleOption(arg, iter); + } else { + filenames.add(arg); + while (iter.hasNext()) { + filenames.add(iter.next()); + } + } + } + + if (noArgs || options.help || + (filenames.isEmpty() && (!options.all || options.classpath.isEmpty()))) { + showHelp(); + } + + if (options.version || options.fullVersion) { + showVersion(options.fullVersion); + } + } + + private void handleOption(String name, Iterator rest) throws BadArgs { + for (Option o : recognizedOptions) { + if (o.matches(name)) { + if (o.hasArg) { + String arg = rest.hasNext() ? rest.next() : "-"; + if (arg.startsWith("-")) { + throw new BadArgs("err.missing.arg", name).showUsage(true); + } else { + o.process(this, name, arg); + } + } else { + o.process(this, name, null); + } + + if (o.ignoreRest()) { + while (rest.hasNext()) { + rest.next(); + } + } + return; + } + } + throw new BadArgs("err.unknown.option", name).showUsage(true); + } + + private void reportError(String key, Object... args) { + log.println(getMessage("error.prefix") + " " + getMessage(key, args)); + } + + private void warning(String key, Object... args) { + log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); + } + + private void showHelp() { + log.println(getMessage("main.usage", PROGNAME)); + for (Option o : recognizedOptions) { + String name = o.aliases[0].substring(1).replace(':', '.'); // there must always be at least one name + if (o.isHidden() || name.equals("h")) { + continue; + } + log.println(getMessage("main.opt." + name)); + } + } + + private void showVersion(boolean full) { + log.println(version(full ? "full" : "release")); + } + + private String version(String key) { + // key=version: mm.nn.oo[-milestone] + // key=full: mm.mm.oo[-milestone]-build + if (ResourceBundleHelper.versionRB == null) { + return System.getProperty("java.version"); + } + try { + return ResourceBundleHelper.versionRB.getString(key); + } catch (MissingResourceException e) { + return getMessage("version.unknown", System.getProperty("java.version")); + } + } + + public String getMessage(String key, Object... args) { + try { + return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args); + } catch (MissingResourceException e) { + throw new InternalError("Missing message: " + key); + } + } + + private static class Options { + enum Verbose { + CLASS, + PACKAGE, + SUMMARY + }; + + public boolean help; + public boolean version; + public boolean fullVersion; + public boolean showFlags; + public boolean reverse; + public boolean all; + public boolean showProfile; + public boolean showSummary; + public String regex; + public String classpath = ""; + public int depth = 1; + public Verbose verbose = Verbose.PACKAGE; + public Set packageNames = new HashSet<>(); + } + + private static class ResourceBundleHelper { + static final ResourceBundle versionRB; + static final ResourceBundle bundle; + + static { + Locale locale = Locale.getDefault(); + try { + bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale); + } catch (MissingResourceException e) { + throw new InternalError("Cannot find jdeps resource bundle for locale " + locale); + } + try { + versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version"); + } catch (MissingResourceException e) { + throw new InternalError("version.resource.missing"); + } + } + } + + private List getArchives(List filenames) throws IOException { + List result = new ArrayList<>(); + for (String s : filenames) { + File f = new File(s); + if (f.exists()) { + ClassFileReader reader = ClassFileReader.newInstance(f); + Archive archive = new Archive(f, reader); + result.add(archive); + } else { + warning("warn.file.not.exist", s); + } + } + return result; + } + + private List classPathArchives(String classpath) throws IOException { + List result = new ArrayList<>(); + if (classpath.isEmpty()) { + return result; + } + for (String p : classpath.split(File.pathSeparator)) { + if (p.length() > 0) { + File f = new File(p); + if (f.exists()) { + ClassFileReader reader = ClassFileReader.newInstance(f); + Archive archive = new Archive(f, reader); + result.add(archive); + } + } + } + return result; + } +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/Main.java 2012-12-06 15:26:12.907918050 +0100 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012, 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.jdeps; + +import java.io.*; + +/** + * + * Usage: + * jdeps [options] files ... + * where options include: + * -p package-name restrict analysis to classes in this package + * (may be given multiple times) + * -e regex restrict analysis to packages matching pattern + * (-p and -e are exclusive) + * -v show class-level dependencies + * default: package-level dependencies + * -r --recursive transitive dependencies analysis + * -classpath paths Classpath to locate class files + * -all process all class files in the given classpath + */ +public class Main { + public static void main(String... args) throws Exception { + JdepsTask t = new JdepsTask(); + int rc = t.run(args); + System.exit(rc); + } + + + /** + * Entry point that does not call System.exit. + * + * @param args command line arguments + * @param out output stream + * @return an exit code. 0 means success, non-zero means an error occurred. + */ + public static int run(String[] args, PrintWriter out) { + JdepsTask t = new JdepsTask(); + t.setLog(out); + return t.run(args); + } +} + --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java 2012-12-06 15:26:13.327990492 +0100 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2012, 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.jdeps; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +/** + * ClassPath for Java SE and JDK + */ +class PlatformClassPath { + /* + * Profiles for Java SE + * + * This is a temporary workaround until a common API is defined for langtools + * to determine which profile a given classname belongs to. The list of + * packages and profile names are hardcoded in jdk.properties and + * split packages are not supported. + */ + static class Profile { + final String name; + final Set packages; + + Profile(String name) { + this.name = name; + this.packages = new HashSet<>(); + } + } + + private final static String JAVAFX = "javafx"; + private final static Map map = getProfilePackages(); + static String getProfileName(String packageName) { + Profile profile = map.get(packageName); + if (packageName.startsWith(JAVAFX + ".")) { + profile = map.get(JAVAFX); + } + return profile != null ? profile.name : ""; + } + + private final static List javaHomeArchives = init(); + static List getArchives() { + return javaHomeArchives; + } + + static boolean contains(Archive archive) { + return javaHomeArchives.contains(archive); + } + + private static List init() { + List result = new ArrayList<>(); + String javaHome = System.getProperty("java.home"); + List files = new ArrayList<>(); + File jre = new File(javaHome, "jre"); + File lib = new File(javaHome, "lib"); + + try { + if (jre.exists() && jre.isDirectory()) { + result.addAll(addJarFiles(new File(jre, "lib"))); + result.addAll(addJarFiles(lib)); + } else if (lib.exists() && lib.isDirectory()) { + // either a JRE or a jdk build image + File classes = new File(javaHome, "classes"); + if (classes.exists() && classes.isDirectory()) { + // jdk build outputdir + ClassFileReader reader = ClassFileReader.newInstance(classes); + Archive archive = new Archive(classes, reader); + result.add(archive); + } + // add other JAR files + result.addAll(addJarFiles(lib)); + } else { + throw new RuntimeException("\"" + javaHome + "\" not a JDK home"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + // add a JavaFX profile if there is jfxrt.jar + for (Archive archive : result) { + if (archive.getFileName().equals("jfxrt.jar")) { + map.put(JAVAFX, new Profile("jfxrt.jar")); + } + } + return result; + } + + private static List addJarFiles(File f) throws IOException { + final List result = new ArrayList<>(); + final Path root = f.toPath(); + final Path ext = root.resolve("ext"); + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException + { + if (dir.equals(root) || dir.equals(ext)) { + return FileVisitResult.CONTINUE; + } else { + // skip other cobundled JAR files + return FileVisitResult.SKIP_SUBTREE; + } + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException + { + String fn = file.toFile().getName(); + if (fn.endsWith(".jar") && !fn.equals("alt-rt.jar")) { + File f = file.toFile(); + ClassFileReader reader = ClassFileReader.newInstance(f); + Archive archive = new Archive(f, reader); + result.add(archive); + } + return FileVisitResult.CONTINUE; + } + }); + return result; + } + + private static Map getProfilePackages() { + Map map = new HashMap<>(); + + // read the properties as a ResourceBundle as the build compiles + // the properties file into Java class. Another alternative is + // to load it as Properties and fix the build to exclude this file. + ResourceBundle profileBundle + = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdk"); + + int i=1; + String key; + while (profileBundle.containsKey((key = "profile." + i + ".name"))) { + Profile profile = new Profile(profileBundle.getString(key)); + String n = profileBundle.getString("profile." + i + ".packages"); + String[] pkgs = n.split("\\s+"); + for (String p : pkgs) { + if (p.isEmpty()) continue; + assert(map.containsKey(p) == false); + profile.packages.add(p); + map.put(p, profile); + } + i++; + } + return map; + } +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties 2012-12-06 15:26:13.838078465 +0100 @@ -0,0 +1,59 @@ +main.usage.summary=\ +Usage: {0} \n\ +use -h or --help for a list of possible options + +main.usage=\ +Usage: {0} \n\ +where possible options include: + +error.prefix=Error: +warn.prefix=Warning: + +main.opt.h=\ +\ -h --help Print this usage message + +main.opt.version=\ +\ -version Version information + +main.opt.v=\ +\ -v --verbose Print class-level dependencies + +main.opt.v.class=\ +\ -v:class Print class-level dependencies + +main.opt.v.package=\ +\ -v:package Print package-level dependencies + +main.opt.summary=\ +\ -summary Print dependency summary only + +main.opt.all=\ +\ -all Process all classes specified in -classpath + +main.opt.p=\ +\ -p Restrict analysis to classes in this package\n\ +\ (may be given multiple times) + +main.opt.e=\ +\ -e Restrict analysis to packages matching pattern\n\ +\ (-p and -e are exclusive) + +main.opt.P=\ +\ -P --profile Show profile or the file containing a package + +main.opt.classpath=\ +\ -classpath Specify where to find class files + +main.opt.R=\ +\ -R --recursive Traverse all dependencies recursively + +main.opt.d=\ +\ -d Specify the depth of the transitive dependency analysis\n\ +\ -r option is equivalent to setting depth to 0. + +err.unknown.option=unknown option: {0} +err.missing.arg=no value given for {0} +err.internal.error=internal error: {0} {1} {2} +err.invalid.arg.for.option=invalid argument for option: {0} +warn.file.not.exist=file does not exist: {0} +warn.split.package=package {0} defined in {1} {2} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdk.properties 2012-12-06 15:26:14.268152631 +0100 @@ -0,0 +1,261 @@ +# This properties file does not need localization. + +profile.1.name = compact1 +profile.1.packages = \ + java.io \ + java.lang \ + java.lang.annotation \ + java.lang.invoke \ + java.lang.ref \ + java.lang.reflect \ + java.math \ + java.net \ + java.nio \ + java.nio.channels \ + java.nio.channels.spi \ + java.nio.charset \ + java.nio.charset.spi \ + java.nio.file \ + java.nio.file.attribute \ + java.nio.file.spi \ + java.security \ + java.security.cert \ + java.security.interfaces \ + java.security.spec \ + java.text \ + java.text.spi \ + java.util \ + java.util.concurrent \ + java.util.concurrent.atomic \ + java.util.concurrent.locks \ + java.util.jar \ + java.util.logging \ + java.util.regex \ + java.util.spi \ + java.util.zip \ + javax.crypto \ + javax.crypto.interfaces \ + javax.crypto.spec \ + javax.security.auth \ + javax.security.auth.callback \ + javax.security.auth.login \ + javax.security.auth.spi \ + javax.security.auth.x500 \ + javax.net \ + javax.net.ssl \ + javax.security.cert \ + \ + com.sun.net.ssl \ + com.sun.nio.file \ + com.sun.nio.sctp \ + com.sun.security.auth \ + com.sun.security.auth.login + +profile.2.name = compact2 +profile.2.packages = \ + java.sql \ + javax.sql \ + javax.xml \ + javax.xml.datatype \ + javax.xml.namespace \ + javax.xml.parsers \ + javax.xml.stream \ + javax.xml.stream.events \ + javax.xml.stream.util \ + javax.xml.transform \ + javax.xml.transform.dom \ + javax.xml.transform.sax \ + javax.xml.transform.stax \ + javax.xml.transform.stream \ + javax.xml.validation \ + javax.xml.xpath \ + org.w3c.dom \ + org.w3c.dom.bootstrap \ + org.w3c.dom.events \ + org.w3c.dom.ls \ + org.xml.sax \ + org.xml.sax.ext \ + org.xml.sax.helpers \ + java.rmi \ + java.rmi.activation \ + java.rmi.dgc \ + java.rmi.registry \ + java.rmi.server \ + javax.rmi.ssl \ + javax.transaction \ + javax.transaction.xa \ + \ + com.sun.net.httpserver \ + com.sun.net.httpserver.spi + +profile.3.name = compact3 +profile.3.packages = \ + java.lang.instrument \ + java.lang.management \ + java.security.acl \ + java.util.prefs \ + javax.management \ + javax.management.loading \ + javax.management.modelmbean \ + javax.management.monitor \ + javax.management.openmbean \ + javax.management.relation \ + javax.management.remote \ + javax.management.remote.rmi \ + javax.management.timer \ + javax.naming \ + javax.naming.directory \ + javax.naming.event \ + javax.naming.ldap \ + javax.naming.spi \ + javax.sql.rowset \ + javax.sql.rowset.serial \ + javax.sql.rowset.spi \ + javax.security.auth.kerberos \ + javax.security.sasl \ + javax.script \ + javax.smartcardio \ + javax.xml.crypto \ + javax.xml.crypto.dom \ + javax.xml.crypto.dsig \ + javax.xml.crypto.dsig.dom \ + javax.xml.crypto.dsig.keyinfo \ + javax.xml.crypto.dsig.spec \ + javax.annotation.processing \ + javax.lang.model \ + javax.lang.model.element \ + javax.lang.model.type \ + javax.lang.model.util \ + javax.tools \ + javax.tools.annotation \ + org.ietf.jgss \ + \ + com.sun.management \ + com.sun.security.auth.callback \ + com.sun.security.auth.module \ + com.sun.security.jgss + +profile.4.name = Full JRE +profile.4.packages = \ + java.applet \ + java.awt \ + java.awt.color \ + java.awt.datatransfer \ + java.awt.dnd \ + java.awt.dnd.peer \ + java.awt.event \ + java.awt.font \ + java.awt.geom \ + java.awt.im \ + java.awt.im.spi \ + java.awt.image \ + java.awt.image.renderable \ + java.awt.peer \ + java.awt.print \ + java.beans \ + java.beans.beancontext \ + javax.accessibility \ + javax.imageio \ + javax.imageio.event \ + javax.imageio.metadata \ + javax.imageio.plugins.bmp \ + javax.imageio.plugins.jpeg \ + javax.imageio.spi \ + javax.imageio.stream \ + javax.print \ + javax.print.attribute \ + javax.print.attribute.standard \ + javax.print.event \ + javax.sound.midi \ + javax.sound.midi.spi \ + javax.sound.sampled \ + javax.sound.sampled.spi \ + javax.swing \ + javax.swing.border \ + javax.swing.colorchooser \ + javax.swing.event \ + javax.swing.filechooser \ + javax.swing.plaf \ + javax.swing.plaf.basic \ + javax.swing.plaf.metal \ + javax.swing.plaf.multi \ + javax.swing.plaf.nimbus \ + javax.swing.plaf.synth \ + javax.swing.table \ + javax.swing.text \ + javax.swing.text.html \ + javax.swing.text.html.parser \ + javax.swing.text.rtf \ + javax.swing.tree \ + javax.swing.undo \ + javax.activation \ + javax.jws \ + javax.jws.soap \ + javax.rmi \ + javax.rmi.CORBA \ + javax.xml.bind \ + javax.xml.bind.annotation \ + javax.xml.bind.annotation.adapters \ + javax.xml.bind.attachment \ + javax.xml.bind.helpers \ + javax.xml.bind.util \ + javax.xml.soap \ + javax.xml.ws \ + javax.xml.ws.handler \ + javax.xml.ws.handler.soap \ + javax.xml.ws.http \ + javax.xml.ws.soap \ + javax.xml.ws.spi \ + javax.xml.ws.spi.http \ + javax.xml.ws.wsaddressing \ + javax.annotation \ + org.omg.CORBA \ + org.omg.CORBA.DynAnyPackage \ + org.omg.CORBA.ORBPackage \ + org.omg.CORBA.TypeCodePackage \ + org.omg.CORBA.portable \ + org.omg.CORBA_2_3 \ + org.omg.CORBA_2_3.portable \ + org.omg.CosNaming \ + org.omg.CosNaming.NamingContextExtPackage \ + org.omg.CosNaming.NamingContextPackage \ + org.omg.Dynamic \ + org.omg.DynamicAny \ + org.omg.DynamicAny.DynAnyFactoryPackage \ + org.omg.DynamicAny.DynAnyPackage \ + org.omg.IOP \ + org.omg.IOP.CodecFactoryPackage \ + org.omg.IOP.CodecPackage \ + org.omg.Messaging \ + org.omg.PortableInterceptor \ + org.omg.PortableInterceptor.ORBInitInfoPackage \ + org.omg.PortableServer \ + org.omg.PortableServer.CurrentPackage \ + org.omg.PortableServer.POAManagerPackage \ + org.omg.PortableServer.POAPackage \ + org.omg.PortableServer.ServantLocatorPackage \ + org.omg.PortableServer.portable \ + org.omg.SendingContext \ + org.omg.stub.java.rmi \ + org.omg.stub.javax.management.remote.rmi + +# Remaining JDK supported API +profile.5.name = JDK tools +profile.5.packages = \ + com.sun.jdi \ + com.sun.jdi.connect \ + com.sun.jdi.connect.spi \ + com.sun.jdi.event \ + com.sun.jdi.request \ + com.sun.javadoc \ + com.sun.tools.doclets \ + com.sun.source.tree \ + com.sun.source.util \ + com.sun.tools.attach \ + com.sun.tools.attach.spi \ + com.sun.tools.jconsole \ + com.sun.tools.javac \ + com.sun.tools.javah \ + com.sun.tools.javap \ + com.sun.tools.javadoc \ + com.sun.servicetag --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/src/share/classes/com/sun/tools/jdeps/resources/version.properties-template 2012-12-06 15:26:14.678223353 +0100 @@ -0,0 +1,28 @@ +# +# Copyright (c) 2012, 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. +# + +jdk=$(JDK_VERSION) +full=$(FULL_VERSION) +release=$(RELEASE) --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/test/tools/jdeps/Basic.java 2012-12-06 15:26:15.118299253 +0100 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2012, 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. + * + * 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 8003562 + * @summary Basic tests for jdeps tool + * @build Test p.Foo + * @run main Basic + */ + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; +import java.util.regex.*; + +public class Basic { + public static void main(String... args) throws Exception { + int errors = 0; + + errors += new Basic().run(); + if (errors > 0) + throw new Exception(errors + " errors found"); + } + + int run() throws IOException { + File testDir = new File(System.getProperty("test.classes", ".")); + // test a .class file + test(new File(testDir, "Test.class"), + new String[] {"java.lang", "p"}); + // test a directory + test(new File(testDir, "p"), + new String[] {"java.lang", "java.util"}); + // test class-level dependency output + test(new File(testDir, "Test.class"), + new String[] {"java.lang.Object", "p.Foo"}, + new String[] {"-v:class"}); + // test -p option + test(new File(testDir, "Test.class"), + new String[] {"p.Foo"}, + new String[] {"-v:class", "-p", "p"}); + // test -e option + test(new File(testDir, "Test.class"), + new String[] {"p.Foo"}, + new String[] {"-v:class", "-e", "p\\..*"}); + test(new File(testDir, "Test.class"), + new String[] {"java.lang"}, + new String[] {"-v:package", "-e", "java\\.lang\\..*"}); + // test -classpath and -all options + test(null, + new String[] {"com.sun.tools.jdeps", "java.lang", "java.util", + "java.util.regex", "java.io", "p"}, + new String[] {"-classpath", testDir.getPath(), "-all"}); + return errors; + } + + void test(File file, String[] expect) { + test(file, expect, new String[0]); + } + + void test(File file, String[] expect, String[] options) { + String[] args; + if (file != null) { + args = Arrays.copyOf(options, options.length+1); + args[options.length] = file.getPath(); + } else { + args = options; + } + String[] deps = jdeps(args); + checkEqual("dependencies", expect, deps); + } + + String[] jdeps(String... args) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + System.err.println("jdeps " + Arrays.toString(args)); + int rc = com.sun.tools.jdeps.Main.run(args, pw); + pw.close(); + String out = sw.toString(); + if (!out.isEmpty()) + System.err.println(out); + if (rc != 0) + throw new Error("jdeps failed: rc=" + rc); + return findDeps(out); + } + + // Pattern used to parse lines + private static Pattern linePattern + = Pattern.compile(".*\r?\n"); + private static Pattern pattern + = Pattern.compile("\\s+ -> (\\S+) +.*"); + + // Use the linePattern to break the given String into lines, applying + // the pattern to each line to see if we have a match + private static String[] findDeps(String out) { + List result = new ArrayList<>(); + Matcher lm = linePattern.matcher(out); // Line matcher + Matcher pm = null; // Pattern matcher + int lines = 0; + while (lm.find()) { + lines++; + CharSequence cs = lm.group(); // The current line + if (pm == null) + pm = pattern.matcher(cs); + else + pm.reset(cs); + if (pm.find()) + result.add(pm.group(1)); + if (lm.end() == out.length()) + break; + } + return result.toArray(new String[0]); + } + + void checkEqual(String label, String[] expect, String[] found) { + Set s1 = new HashSet<>(Arrays.asList(expect)); + Set s2 = new HashSet<>(Arrays.asList(found)); + + if (!s1.equals(s2)) + error("Unexpected " + label + " found: '" + s2 + "', expected: '" + s1 + "'"); + } + + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + int errors; +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/test/tools/jdeps/Test.java 2012-12-06 15:26:15.548373433 +0100 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012, 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. + * + * 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. + */ + +public class Test { + public void test() { + p.Foo f = new p.Foo(); + } +} --- /dev/null 2012-10-24 11:46:27.564410214 +0200 +++ new/langtools/test/tools/jdeps/p/Foo.java 2012-12-06 15:26:16.028456247 +0100 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, 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. + * + * 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 p; + +import java.util.List; +import java.util.Collections; +public class Foo { + public static List foo() { + return Collections.emptyList(); + } + + public Foo() { + } +}