8003562: Provide a CLI tool to analyze class dependencies
authormchung
Fri, 28 Dec 2012 22:25:21 -0800
changeset 15030 2d8dec41f029
parent 14965 bb1eb01b8c41
child 15031 c4fad55a5681
8003562: Provide a CLI tool to analyze class dependencies Reviewed-by: jjg, alanb, ulfzibis, erikj
langtools/make/build.properties
langtools/makefiles/BuildLangtools.gmk
langtools/src/share/classes/com/sun/tools/classfile/Dependencies.java
langtools/src/share/classes/com/sun/tools/classfile/Dependency.java
langtools/src/share/classes/com/sun/tools/jdeps/Archive.java
langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java
langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
langtools/src/share/classes/com/sun/tools/jdeps/Main.java
langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java
langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
langtools/src/share/classes/com/sun/tools/jdeps/resources/jdk.properties
langtools/src/share/classes/com/sun/tools/jdeps/resources/version.properties-template
langtools/test/Makefile
langtools/test/tools/jdeps/Basic.java
langtools/test/tools/jdeps/Test.java
langtools/test/tools/jdeps/p/Foo.java
--- a/langtools/make/build.properties	Tue Dec 25 17:23:59 2012 -0800
+++ b/langtools/make/build.properties	Fri Dec 28 22:25:21 2012 -0800
@@ -153,6 +153,7 @@
 javap.includes = \
         com/sun/tools/classfile/ \
         com/sun/tools/javap/ \
+        com/sun/tools/jdeps/ \
         sun/tools/javap/
 
 javap.tests = \
--- a/langtools/makefiles/BuildLangtools.gmk	Tue Dec 25 17:23:59 2012 -0800
+++ b/langtools/makefiles/BuildLangtools.gmk	Fri Dec 28 22:25:21 2012 -0800
@@ -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 > $@
 
--- a/langtools/src/share/classes/com/sun/tools/classfile/Dependencies.java	Tue Dec 25 17:23:59 2012 -0800
+++ b/langtools/src/share/classes/com/sun/tools/classfile/Dependencies.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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('/', '.').replace('$', '.');
         }
 
-        /**
-         * 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 getName() {
+            return name;
+        }
+
         public String getClassName() {
             return className;
         }
 
+        public String getPackageName() {
+            int i = name.lastIndexOf('/');
+            return (i > 0) ? name.substring(0, i).replace('/', '.') : "";
+        }
+
         @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;
             }
--- a/langtools/src/share/classes/com/sun/tools/classfile/Dependency.java	Tue Dec 25 17:23:59 2012 -0800
+++ b/langtools/src/share/classes/com/sun/tools/classfile/Dependency.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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();
     }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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 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<String,Archive> archiveForClass = new HashMap<String,Archive>();
+    public static Archive find(Location loc) {
+        return archiveForClass.get(loc.getName());
+    }
+
+    private final File file;
+    private final String filename;
+    private final DependencyRecorder recorder;
+    private final ClassFileReader reader;
+    public Archive(String name) {
+        this.file = null;
+        this.filename = name;
+        this.recorder = new DependencyRecorder();
+        this.reader = null;
+    }
+
+    public Archive(File f, ClassFileReader reader) {
+        this.file = f;
+        this.filename = f.getName();
+        this.recorder = new DependencyRecorder();
+        this.reader = reader;
+    }
+
+    public ClassFileReader reader() {
+        return reader;
+    }
+
+    public String getFileName() {
+        return filename;
+    }
+
+    public void addClass(String classFileName) {
+        Archive a = archiveForClass.get(classFileName);
+        assert(a == null || a == this); // ## issue warning?
+        if (!archiveForClass.containsKey(classFileName)) {
+            archiveForClass.put(classFileName, this);
+        }
+    }
+
+    public void addDependency(Dependency d) {
+        recorder.addDependency(d);
+    }
+
+    /**
+     * Returns a sorted map of a class to its dependencies.
+     */
+    public SortedMap<Location, SortedSet<Location>> getDependencies() {
+        DependencyRecorder.Filter filter = new DependencyRecorder.Filter() {
+            public boolean accept(Location origin, Location target) {
+                 return (archiveForClass.get(origin.getName()) !=
+                            archiveForClass.get(target.getName()));
+        }};
+
+        SortedMap<Location, SortedSet<Location>> result =
+            new TreeMap<Location, SortedSet<Location>>(locationComparator);
+        for (Map.Entry<Location, Set<Location>> e : recorder.dependencies().entrySet()) {
+            Location o = e.getKey();
+            for (Location t : e.getValue()) {
+                if (filter.accept(o, t)) {
+                    SortedSet<Location> odeps = result.get(o);
+                    if (odeps == null) {
+                        odeps = new TreeSet<Location>(locationComparator);
+                        result.put(o, odeps);
+                    }
+                    odeps.add(t);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the set of archives this archive requires.
+     */
+    public Set<Archive> getRequiredArchives() {
+        SortedSet<Archive> deps = new TreeSet<Archive>(new Comparator<Archive>() {
+            public int compare(Archive a1, Archive a2) {
+                return a1.toString().compareTo(a2.toString());
+            }
+        });
+
+        for (Map.Entry<Location, Set<Location>> e : recorder.dependencies().entrySet()) {
+            Location o = e.getKey();
+            Archive origin = Archive.find(o);
+            for (Location t : e.getValue()) {
+                Archive target = Archive.find(t);
+                assert(origin != null && target != null);
+                if (origin != target) {
+                    if (!deps.contains(target)) {
+                        deps.add(target);
+                    }
+                }
+            }
+        }
+        return deps;
+    }
+
+    public String toString() {
+        return file != null ? file.getPath() : filename;
+    }
+
+    private static class DependencyRecorder {
+        static interface Filter {
+            boolean accept(Location origin, Location target);
+        }
+
+        public void addDependency(Dependency d) {
+            Set<Location> odeps = map.get(d.getOrigin());
+            if (odeps == null) {
+                odeps = new HashSet<Location>();
+                map.put(d.getOrigin(), odeps);
+            }
+            odeps.add(d.getTarget());
+        }
+
+        public Map<Location, Set<Location>> dependencies() {
+            return map;
+        }
+
+        private final Map<Location, Set<Location>> map =
+            new HashMap<Location, Set<Location>>();
+    }
+
+    private static Comparator<Location> locationComparator =
+        new Comparator<Location>() {
+            public int compare(Location o1, Location o2) {
+                return o1.toString().compareTo(o2.toString());
+            }
+        };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,326 @@
+/*
+ * 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() != null
+                                ? path.getFileName().toString()
+                                : path.toString();
+    }
+
+    public String getFileName() {
+        return baseFileName;
+    }
+
+    /**
+     * Returns the ClassFile matching the given binary name
+     * or a fully-qualified class name.
+     */
+    public ClassFile getClassFile(String name) throws IOException {
+        if (name.indexOf('.') > 0) {
+            int i = name.lastIndexOf('.');
+            String pathname = name.replace('.', File.separatorChar) + ".class";
+            if (baseFileName.equals(pathname) ||
+                    baseFileName.equals(pathname.substring(0, i) + "$" +
+                                        pathname.substring(i+1, pathname.length()))) {
+                return readClassFile(path);
+            }
+        } else {
+            if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) {
+                return readClassFile(path);
+            }
+        }
+        return null;
+    }
+
+    public Iterable<ClassFile> getClassFiles() throws IOException {
+        return new Iterable<ClassFile>() {
+            public Iterator<ClassFile> iterator() {
+                return new FileIterator();
+            }
+        };
+    }
+
+    protected ClassFile readClassFile(Path p) throws IOException {
+        InputStream is = null;
+        try {
+            is = Files.newInputStream(p);
+            return ClassFile.read(is);
+        } catch (ConstantPoolException e) {
+            throw new ClassFileError(e);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    class FileIterator implements Iterator<ClassFile> {
+        int count;
+        FileIterator() {
+            this.count = 0;
+        }
+        public boolean hasNext() {
+            return count == 0 && baseFileName.endsWith(".class");
+        }
+
+        public ClassFile next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            try {
+                ClassFile cf = readClassFile(path);
+                count++;
+                return cf;
+            } catch (IOException 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);
+        }
+
+        public ClassFile getClassFile(String name) throws IOException {
+            if (name.indexOf('.') > 0) {
+                int i = name.lastIndexOf('.');
+                String pathname = name.replace('.', File.separatorChar) + ".class";
+                Path p = path.resolve(pathname);
+                if (!p.toFile().exists()) {
+                    p = path.resolve(pathname.substring(0, i) + "$" +
+                                     pathname.substring(i+1, pathname.length()));
+                }
+                if (p.toFile().exists()) {
+                    return readClassFile(p);
+                }
+            } else {
+                Path p = path.resolve(name + ".class");
+                if (p.toFile().exists()) {
+                    return readClassFile(p);
+                }
+            }
+            return null;
+        }
+
+        public Iterable<ClassFile> getClassFiles() throws IOException {
+            final Iterator<ClassFile> iter = new DirectoryIterator();
+            return new Iterable<ClassFile>() {
+                public Iterator<ClassFile> iterator() {
+                    return iter;
+                }
+            };
+        }
+
+        private List<Path> walkTree(Path dir) throws IOException {
+            final List<Path> files = new ArrayList<Path>();
+            Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+                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<ClassFile> {
+            private List<Path> 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 {
+                    return readClassFile(path);
+                } catch (IOException 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 name) throws IOException {
+            if (name.indexOf('.') > 0) {
+                int i = name.lastIndexOf('.');
+                String entryName = name.replace('.', '/') + ".class";
+                JarEntry e = jarfile.getJarEntry(entryName);
+                if (e == null) {
+                    e = jarfile.getJarEntry(entryName.substring(0, i) + "$"
+                            + entryName.substring(i + 1, entryName.length()));
+                }
+                if (e != null) {
+                    return readClassFile(e);
+                }
+            } else {
+                JarEntry e = jarfile.getJarEntry(name + ".class");
+                if (e != null) {
+                    return readClassFile(e);
+                }
+            }
+            return null;
+        }
+
+        private ClassFile readClassFile(JarEntry e) throws IOException {
+            InputStream is = null;
+            try {
+                is = jarfile.getInputStream(e);
+                return ClassFile.read(is);
+            } catch (ConstantPoolException ex) {
+                throw new ClassFileError(ex);
+            } finally {
+                if (is != null)
+                    is.close();
+            }
+        }
+
+        public Iterable<ClassFile> getClassFiles() throws IOException {
+            final Iterator<ClassFile> iter = new JarFileIterator();
+            return new Iterable<ClassFile>() {
+                public Iterator<ClassFile> iterator() {
+                    return iter;
+                }
+            };
+        }
+
+        class JarFileIterator implements Iterator<ClassFile> {
+            private Enumeration<JarEntry> 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 = readClassFile(nextEntry);
+                } catch (IOException 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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,650 @@
+/*
+ * 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;
+                } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
+                    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 abstract class HiddenOption extends Option {
+        HiddenOption(boolean hasArg, String... aliases) {
+            super(hasArg, aliases);
+        }
+
+        boolean isHidden() {
+            return true;
+        }
+    }
+
+    static Option[] recognizedOptions = {
+        new Option(false, "-h", "-?", "--help") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.help = true;
+            }
+        },
+        new Option(false, "-s", "--summary") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.showSummary = true;
+                task.options.verbose = Options.Verbose.SUMMARY;
+            }
+        },
+        new Option(false, "-v", "--verbose") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.verbose = Options.Verbose.VERBOSE;
+            }
+        },
+        new Option(true, "-V", "--verbose-level") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                switch (arg) {
+                    case "package":
+                        task.options.verbose = Options.Verbose.PACKAGE;
+                        break;
+                    case "class":
+                        task.options.verbose = Options.Verbose.CLASS;
+                        break;
+                    default:
+                        throw task.new BadArgs("err.invalid.arg.for.option", opt);
+                }
+            }
+        },
+        new Option(true, "-c", "--classpath") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.classpath = arg;
+            }
+        },
+        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 HiddenOption(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);
+                }
+            }
+        },
+        new Option(false, "--version") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.version = true;
+            }
+        },
+        new HiddenOption(false, "--fullversion") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.fullVersion = true;
+            }
+        },
+
+    };
+
+    private static final String PROGNAME = "jdeps";
+    private final Options options = new Options();
+    private final List<String> classes = new ArrayList<String>();
+
+    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(args);
+            if (options.help) {
+                showHelp();
+            }
+            if (options.version || options.fullVersion) {
+                showVersion(options.fullVersion);
+            }
+            if (classes.isEmpty() && !options.wildcard) {
+                if (options.help || options.version || options.fullVersion) {
+                    return EXIT_OK;
+                } else {
+                    showHelp();
+                    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 final List<Archive> sourceLocations = new ArrayList<Archive>();
+    private final Archive NOT_FOUND = new Archive(getMessage("artifact.not.found"));
+    private boolean run() throws IOException {
+        findDependencies();
+        switch (options.verbose) {
+            case 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 boolean isValidClassName(String name) {
+        if (!Character.isJavaIdentifierStart(name.charAt(0))) {
+            return false;
+        }
+        for (int i=1; i < name.length(); i++) {
+            char c = name.charAt(i);
+            if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void findDependencies() 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());
+                }
+            };
+        }
+
+        List<Archive> archives = new ArrayList<Archive>();
+        Deque<String> roots = new LinkedList<String>();
+        for (String s : classes) {
+            File f = new File(s);
+            if (f.exists()) {
+                archives.add(new Archive(f, ClassFileReader.newInstance(f)));
+            } else {
+                if (isValidClassName(s)) {
+                    roots.add(s);
+                } else {
+                    warning("warn.invalid.arg", s);
+                }
+            }
+        }
+
+        List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
+        if (options.wildcard) {
+            // include all archives from classpath to the initial list
+            archives.addAll(getClassPathArchives(options.classpath));
+        } else {
+            classpaths.addAll(getClassPathArchives(options.classpath));
+        }
+        classpaths.addAll(PlatformClassPath.getArchives());
+
+        // add all archives to the source locations for reporting
+        sourceLocations.addAll(archives);
+        sourceLocations.addAll(classpaths);
+
+        // 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<String> deque = new LinkedList<String>();
+        Set<String> doneClasses = new HashSet<String>();
+
+        // get the immediate dependencies of the input files
+        for (Archive a : archives) {
+            for (ClassFile cf : a.reader().getClassFiles()) {
+                String classFileName;
+                try {
+                    classFileName = cf.getName();
+                } catch (ConstantPoolException e) {
+                    throw new ClassFileError(e);
+                }
+                a.addClass(classFileName);
+                if (!doneClasses.contains(classFileName)) {
+                    doneClasses.add(classFileName);
+                }
+                for (Dependency d : finder.findDependencies(cf)) {
+                    if (filter.accepts(d)) {
+                        String cn = d.getTarget().getName();
+                        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
+        Deque<String> unresolved = roots;
+        int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
+        do {
+            String name;
+            while ((name = unresolved.poll()) != null) {
+                if (doneClasses.contains(name)) {
+                    continue;
+                }
+                ClassFile cf = null;
+                for (Archive a : classpaths) {
+                    cf = a.reader().getClassFile(name);
+                    if (cf != null) {
+                        String classFileName;
+                        try {
+                            classFileName = cf.getName();
+                        } catch (ConstantPoolException e) {
+                            throw new ClassFileError(e);
+                        }
+                        a.addClass(classFileName);
+                        if (!doneClasses.contains(classFileName)) {
+                            // if name is a fully-qualified class name specified
+                            // from command-line, this class might already be parsed
+                            doneClasses.add(classFileName);
+                            if (depth > 0) {
+                                for (Dependency d : finder.findDependencies(cf)) {
+                                    if (filter.accepts(d)) {
+                                        String cn = d.getTarget().getName();
+                                        if (!doneClasses.contains(cn) && !deque.contains(cn)) {
+                                            deque.add(cn);
+                                        }
+                                        a.addDependency(d);
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                }
+                if (cf == null) {
+                    NOT_FOUND.addClass(name);
+                }
+            }
+            unresolved = deque;
+            deque = new LinkedList<String>();
+        } while (!unresolved.isEmpty() && depth-- > 0);
+    }
+
+    private void printPackageDeps(PrintWriter out) {
+        for (Archive source : sourceLocations) {
+            SortedMap<Location, SortedSet<Location>> deps = source.getDependencies();
+            if (deps.isEmpty())
+                continue;
+
+            for (Archive target : source.getRequiredArchives()) {
+                out.format("%s -> %s%n", source, target);
+            }
+
+            Map<String, Archive> pkgs = new TreeMap<String, Archive>();
+            SortedMap<String, Archive> targets = new TreeMap<String, Archive>();
+            String pkg = "";
+            for (Map.Entry<Location, SortedSet<Location>> e : deps.entrySet()) {
+                String cn = e.getKey().getClassName();
+                String p = packageOf(e.getKey());
+                Archive origin = Archive.find(e.getKey());
+                assert origin != null;
+                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, origin.getFileName());
+                }
+
+                for (Location t : e.getValue()) {
+                    p = packageOf(t);
+                    Archive target = Archive.find(t);
+                    if (!targets.containsKey(p)) {
+                        targets.put(p, target);
+                    }
+                }
+            }
+            printTargets(out, targets);
+            out.println();
+        }
+    }
+
+    private void printTargets(PrintWriter out, Map<String, Archive> targets) {
+        for (Map.Entry<String, Archive> 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 pkg = loc.getPackageName();
+        return pkg.isEmpty() ? "<unnamed>" : pkg;
+    }
+
+    private void printClassDeps(PrintWriter out) {
+        for (Archive source : sourceLocations) {
+            SortedMap<Location, SortedSet<Location>> 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<Location, SortedSet<Location>> e : deps.entrySet()) {
+                String cn = e.getKey().getClassName();
+                Archive origin = Archive.find(e.getKey());
+                out.format("   %s (%s)%n", cn, origin.getFileName());
+                for (Location t : e.getValue()) {
+                    cn = t.getClassName();
+                    Archive target = Archive.find(t);
+                    out.format("      -> %-60s %s%n", cn, getPackageInfo(t.getPackageName(), target));
+                }
+            }
+            out.println();
+        }
+    }
+    public void handleOptions(String[] args) throws BadArgs {
+        // process options
+        for (int i=0; i < args.length; i++) {
+            if (args[i].charAt(0) == '-') {
+                String name = args[i];
+                Option option = getOption(name);
+                String param = null;
+                if (option.hasArg) {
+                    if (name.startsWith("--") && name.indexOf('=') > 0) {
+                        param = name.substring(name.indexOf('=') + 1, name.length());
+                    } else if (i + 1 < args.length) {
+                        param = args[++i];
+                    }
+                    if (param == null || param.isEmpty() || param.charAt(0) == '-') {
+                        throw new BadArgs("err.missing.arg", name).showUsage(true);
+                    }
+                }
+                option.process(this, name, param);
+                if (option.ignoreRest()) {
+                    i = args.length;
+                }
+            } else {
+                // process rest of the input arguments
+                for (; i < args.length; i++) {
+                    String name = args[i];
+                    if (name.charAt(0) == '-') {
+                        throw new BadArgs("err.option.after.class", name).showUsage(true);
+                    }
+                    if (name.equals("*") || name.equals("\"*\"")) {
+                        options.wildcard = true;
+                    } else {
+                        classes.add(name);
+                    }
+                }
+            }
+        }
+    }
+
+    private Option getOption(String name) throws BadArgs {
+        for (Option o : recognizedOptions) {
+            if (o.matches(name)) {
+                return o;
+            }
+        }
+        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); // there must always be at least one name
+            name = name.charAt(0) == '-' ? name.substring(1) : 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,
+            VERBOSE
+        };
+
+        boolean help;
+        boolean version;
+        boolean fullVersion;
+        boolean showFlags;
+        boolean showProfile;
+        boolean showSummary;
+        boolean wildcard;
+        String regex;
+        String classpath = "";
+        int depth = 1;
+        Verbose verbose = Verbose.PACKAGE;
+        Set<String> packageNames = new HashSet<String>();
+    }
+
+    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<Archive> getArchives(List<String> filenames) throws IOException {
+        List<Archive> result = new ArrayList<Archive>();
+        for (String s : filenames) {
+            File f = new File(s);
+            if (f.exists()) {
+                result.add(new Archive(f, ClassFileReader.newInstance(f)));
+            } else {
+                warning("warn.file.not.exist", s);
+            }
+        }
+        return result;
+    }
+
+    private List<Archive> getClassPathArchives(String paths) throws IOException {
+        List<Archive> result = new ArrayList<Archive>();
+        if (paths.isEmpty()) {
+            return result;
+        }
+        for (String p : paths.split(File.pathSeparator)) {
+            if (p.length() > 0) {
+                File f = new File(p);
+                if (f.exists()) {
+                    result.add(new Archive(f, ClassFileReader.newInstance(f)));
+                }
+            }
+        }
+        return result;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Main.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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 <i>not</i> 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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,169 @@
+/*
+ * 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<String> packages;
+
+        Profile(String name) {
+            this.name = name;
+            this.packages = new HashSet<String>();
+        }
+    }
+
+    private final static String JAVAFX = "javafx";
+    private final static Map<String,Profile> 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<Archive> javaHomeArchives = init();
+    static List<Archive> getArchives() {
+        return javaHomeArchives;
+    }
+
+    static boolean contains(Archive archive) {
+        return javaHomeArchives.contains(archive);
+    }
+
+    private static List<Archive> init() {
+        List<Archive> result = new ArrayList<Archive>();
+        String javaHome = System.getProperty("java.home");
+        List<File> files = new ArrayList<File>();
+        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
+                    result.add(new Archive(classes, ClassFileReader.newInstance(classes)));
+                }
+                // 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<Archive> addJarFiles(File f) throws IOException {
+        final List<Archive> result = new ArrayList<Archive>();
+        final Path root = f.toPath();
+        final Path ext = root.resolve("ext");
+        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
+            @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
+            {
+                File f = file.toFile();
+                String fn = f.getName();
+                if (fn.endsWith(".jar") && !fn.equals("alt-rt.jar")) {
+                    result.add(new Archive(f, ClassFileReader.newInstance(f)));
+                }
+                return FileVisitResult.CONTINUE;
+            }
+        });
+        return result;
+    }
+
+    private static Map<String,Profile> getProfilePackages() {
+        Map<String,Profile> map = new HashMap<String,Profile>();
+
+        // 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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,57 @@
+main.usage.summary=\
+Usage: {0} <options> <classes...>\n\
+use -h, -? or --help for a list of possible options
+
+main.usage=\
+Usage: {0} <options> <classes...>\n\
+where <classes> can be a pathname to a .class file, a directory, a JAR file,\n\
+or a fully-qualified classname or wildcard "*".  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 <level> --verbose-level=<level>   Print package-level or class-level dependencies\n\
+\                                       Valid levels are: "package" and "class"
+
+main.opt.v=\
+\  -v         --verbose                 Print additional information
+
+main.opt.s=\
+\  -s         --summary                 Print dependency summary only
+
+main.opt.p=\
+\  -p <pkg name> --package=<pkg name>   Restrict analysis to classes in this package\n\
+\                                       (may be given multiple times)
+
+main.opt.e=\
+\  -e <regex> --regex=<regex>           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.c=\
+\  -c <path>  --classpath=<path>        Specify where to find class files
+
+main.opt.R=\
+\  -R         --recursive               Recursively traverse all dependencies
+
+main.opt.d=\
+\  -d <depth> --depth=<depth>           Specify the depth of the transitive dependency analysis
+
+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}
+err.option.after.class=option must be specified before classes: {0}
+warn.invalid.arg=Invalid classname or pathname not exist: {0}
+warn.split.package=package {0} defined in {1} {2}
+
+artifact.not.found=not found
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdk.properties	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,262 @@
+# 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.tools.doctree \
+    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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/version.properties-template	Fri Dec 28 22:25:21 2012 -0800
@@ -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)
--- a/langtools/test/Makefile	Tue Dec 25 17:23:59 2012 -0800
+++ b/langtools/test/Makefile	Fri Dec 28 22:25:21 2012 -0800
@@ -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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/Basic.java	Fri Dec 28 22:25:21 2012 -0800
@@ -0,0 +1,149 @@
+/*
+ * 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[] {"--verbose-level=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(), "*"});
+        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<String> 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<String> s1 = new HashSet<>(Arrays.asList(expect));
+        Set<String> 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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/Test.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/p/Foo.java	Fri Dec 28 22:25:21 2012 -0800
@@ -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() {
+    }
+}