8029548: (jdeps) use @jdk.Exported to determine supported vs JDK internal API
authormchung
Tue, 08 Jul 2014 18:26:34 -0700
changeset 25442 755ff386d1ac
parent 25441 92dcee5a4741
child 25443 9187d77f2c64
8029548: (jdeps) use @jdk.Exported to determine supported vs JDK internal API 8031092: jdeps does not recognize --help option. 8048063: (jdeps) Add filtering capability Reviewed-by: alanb, dfuchs
langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.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/Profile.java
langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
langtools/test/tools/jdeps/APIDeps.java
langtools/test/tools/jdeps/Basic.java
langtools/test/tools/jdeps/DotFileTest.java
langtools/test/tools/jdeps/m/Gee.java
langtools/test/tools/jdeps/p/Bar.java
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,16 +26,13 @@
 
 import com.sun.tools.classfile.Dependency.Location;
 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
-import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
 
 /**
  * Dependency Analyzer.
@@ -52,7 +49,16 @@
         VERBOSE
     }
 
+    /**
+     * Filter to be applied when analyzing the dependencies from the given archives.
+     * Only the accepted dependencies are recorded.
+     */
+    interface Filter {
+        boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
+    }
+
     private final Type type;
+    private final Filter filter;
     private final Map<Archive, ArchiveDeps> results = new HashMap<>();
     private final Map<Location, Archive> map = new HashMap<>();
     private final Archive NOT_FOUND
@@ -62,9 +68,11 @@
      * Constructs an Analyzer instance.
      *
      * @param type Type of the dependency analysis
+     * @param filter
      */
-    public Analyzer(Type type) {
+    public Analyzer(Type type, Filter filter) {
         this.type = type;
+        this.filter = filter;
     }
 
     /**
@@ -72,6 +80,18 @@
      */
     public void run(List<Archive> archives) {
         // build a map from Location to Archive
+        buildLocationArchiveMap(archives);
+
+        // traverse and analyze all dependencies
+        for (Archive archive : archives) {
+            ArchiveDeps deps = new ArchiveDeps(archive, type);
+            archive.visitDependences(deps);
+            results.put(archive, deps);
+        }
+    }
+
+    private void buildLocationArchiveMap(List<Archive> archives) {
+        // build a map from Location to Archive
         for (Archive archive: archives) {
             for (Location l: archive.getClasses()) {
                 if (!map.containsKey(l)) {
@@ -81,190 +101,202 @@
                 }
             }
         }
-        // traverse and analyze all dependencies
-        for (Archive archive : archives) {
-            ArchiveDeps deps;
-            if (type == Type.CLASS || type == Type.VERBOSE) {
-                deps = new ClassVisitor(archive);
-            } else {
-                deps = new PackageVisitor(archive);
-            }
-            archive.visitDependences(deps);
-            results.put(archive, deps);
-        }
     }
 
     public boolean hasDependences(Archive archive) {
         if (results.containsKey(archive)) {
-            return results.get(archive).deps.size() > 0;
+            return results.get(archive).dependencies().size() > 0;
         }
         return false;
     }
 
     public interface Visitor {
         /**
-         * Visits the source archive to its destination archive of
-         * a recorded dependency.
-         */
-        void visitArchiveDependence(Archive origin, Archive target, Profile profile);
-        /**
          * Visits a recorded dependency from origin to target which can be
-         * a fully-qualified classname, a package name, a profile or
+         * a fully-qualified classname, a package name, a module or
          * archive name depending on the Analyzer's type.
          */
-        void visitDependence(String origin, Archive source, String target, Archive archive, Profile profile);
+        public void visitDependence(String origin, Archive originArchive,
+                                    String target, Archive targetArchive);
     }
 
-    public void visitArchiveDependences(Archive source, Visitor v) {
-        ArchiveDeps r = results.get(source);
-        for (ArchiveDeps.Dep d: r.requireArchives()) {
-            v.visitArchiveDependence(r.archive, d.archive, d.profile);
+    /**
+     * Visit the dependencies of the given source.
+     * If the requested level is SUMMARY, it will visit the required archives list.
+     */
+    public void visitDependences(Archive source, Visitor v, Type level) {
+        if (level == Type.SUMMARY) {
+            final ArchiveDeps result = results.get(source);
+            result.requires().stream()
+                  .sorted(Comparator.comparing(Archive::getName))
+                  .forEach(archive -> {
+                      Profile profile = result.getTargetProfile(archive);
+                      v.visitDependence(source.getName(), source,
+                                        profile != null ? profile.profileName() : archive.getName(), archive);
+                  });
+        } else {
+            ArchiveDeps result = results.get(source);
+            if (level != type) {
+                // requesting different level of analysis
+                result = new ArchiveDeps(source, level);
+                source.visitDependences(result);
+            }
+            result.dependencies().stream()
+                  .sorted(Comparator.comparing(Dep::origin)
+                                    .thenComparing(Dep::target))
+                  .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive()));
         }
     }
 
     public void visitDependences(Archive source, Visitor v) {
-        ArchiveDeps r = results.get(source);
-        for (Map.Entry<String, SortedSet<ArchiveDeps.Dep>> e: r.deps.entrySet()) {
-            String origin = e.getKey();
-            for (ArchiveDeps.Dep d: e.getValue()) {
-                // filter intra-dependency unless in verbose mode
-                if (type == Type.VERBOSE || d.archive != source) {
-                    v.visitDependence(origin, source, d.target, d.archive, d.profile);
-                }
-            }
-        }
+        visitDependences(source, v, type);
     }
 
     /**
-     * ArchiveDeps contains the dependencies for an Archive that
-     * can have one or more classes.
+     * ArchiveDeps contains the dependencies for an Archive that can have one or
+     * more classes.
      */
-    private abstract class ArchiveDeps implements Archive.Visitor {
-        final Archive archive;
-        final SortedMap<String, SortedSet<Dep>> deps;
-        ArchiveDeps(Archive archive) {
+    class ArchiveDeps implements Archive.Visitor {
+        protected final Archive archive;
+        protected final Set<Archive> requires;
+        protected final Set<Dep> deps;
+        protected final Type level;
+        private Profile profile;
+        ArchiveDeps(Archive archive, Type level) {
             this.archive = archive;
-            this.deps = new TreeMap<>();
-        }
-
-        void add(String origin, String target, Archive targetArchive, String pkgName) {
-            SortedSet<Dep> set = deps.get(origin);
-            if (set == null) {
-                deps.put(origin, set = new TreeSet<>());
-            }
-            Profile p = targetArchive instanceof JDKArchive
-                            ? Profile.getProfile(pkgName) : null;
-            set.add(new Dep(target, targetArchive, p));
+            this.deps = new HashSet<>();
+            this.requires = new HashSet<>();
+            this.level = level;
         }
 
-        /**
-         * Returns the list of Archive dependences.  The returned
-         * list contains one {@code Dep} instance per one archive
-         * and with the minimum profile this archive depends on.
-         */
-        List<Dep> requireArchives() {
-            Map<Archive,Profile> map = new HashMap<>();
-            for (Set<Dep> set: deps.values()) {
-                for (Dep d: set) {
-                    if (this.archive != d.archive) {
-                        Profile p = map.get(d.archive);
-                        if (p == null || (d.profile != null && p.profile < d.profile.profile)) {
-                            map.put(d.archive, d.profile);
-                        }
-                    }
-                }
+        Set<Dep> dependencies() {
+            return deps;
+        }
+
+        Set<Archive> requires() {
+            return requires;
+        }
+
+        Profile getTargetProfile(Archive target) {
+            return JDKArchive.isProfileArchive(target) ? profile : null;
+        }
+
+        Archive findArchive(Location t) {
+            Archive target = archive.getClasses().contains(t) ? archive : map.get(t);
+            if (target == null) {
+                map.put(t, target = NOT_FOUND);
             }
-            List<Dep> list = new ArrayList<>();
-            for (Map.Entry<Archive,Profile> e: map.entrySet()) {
-                list.add(new Dep("", e.getKey(), e.getValue()));
-            }
-            return list;
+            return target;
         }
 
-        /**
-         * Dep represents a dependence where the target can be
-         * a classname or packagename and the archive and profile
-         * the target belongs to.
-         */
-        class Dep implements Comparable<Dep> {
-            final String target;
-            final Archive archive;
-            final Profile profile;
-            Dep(String target, Archive archive, Profile p) {
-                this.target = target;
-                this.archive = archive;
-                this.profile = p;
+        // return classname or package name depedning on the level
+        private String getLocationName(Location o) {
+            if (level == Type.CLASS || level == Type.VERBOSE) {
+                return o.getClassName();
+            } else {
+                String pkg = o.getPackageName();
+                return pkg.isEmpty() ? "<unnamed>" : pkg;
+            }
+        }
+
+        @Override
+        public void visit(Location o, Location t) {
+            Archive targetArchive = findArchive(t);
+            if (filter.accepts(o, archive, t, targetArchive)) {
+                addDep(o, t);
+                if (!requires.contains(targetArchive)) {
+                    requires.add(targetArchive);
+                }
             }
+            if (targetArchive instanceof JDKArchive) {
+                Profile p = Profile.getProfile(t.getPackageName());
+                if (profile == null || (p != null && p.compareTo(profile) > 0)) {
+                    profile = p;
+                }
+            }
+        }
 
-            @Override
-            public boolean equals(Object o) {
-                if (o instanceof Dep) {
-                    Dep d = (Dep)o;
-                    return this.archive == d.archive && this.target.equals(d.target);
-                }
-                return false;
+        private Dep curDep;
+        protected Dep addDep(Location o, Location t) {
+            String origin = getLocationName(o);
+            String target = getLocationName(t);
+            Archive targetArchive = findArchive(t);
+            if (curDep != null &&
+                    curDep.origin().equals(origin) &&
+                    curDep.originArchive() == archive &&
+                    curDep.target().equals(target) &&
+                    curDep.targetArchive() == targetArchive) {
+                return curDep;
             }
 
-            @Override
-            public int hashCode() {
-                int hash = 3;
-                hash = 17 * hash + Objects.hashCode(this.archive);
-                hash = 17 * hash + Objects.hashCode(this.target);
-                return hash;
-            }
-
-            @Override
-            public int compareTo(Dep o) {
-                if (this.target.equals(o.target)) {
-                    if (this.archive == o.archive) {
-                        return 0;
-                    } else {
-                        return this.archive.getFileName().compareTo(o.archive.getFileName());
+            Dep e = new Dep(origin, archive, target, targetArchive);
+            if (deps.contains(e)) {
+                for (Dep e1 : deps) {
+                    if (e.equals(e1)) {
+                        curDep = e1;
                     }
                 }
-                return this.target.compareTo(o.target);
+            } else {
+                deps.add(e);
+                curDep = e;
             }
-        }
-        public abstract void visit(Location o, Location t);
-    }
-
-    private class ClassVisitor extends ArchiveDeps {
-        ClassVisitor(Archive archive) {
-            super(archive);
-        }
-        @Override
-        public void visit(Location o, Location t) {
-            Archive targetArchive =
-                this.archive.getClasses().contains(t) ? this.archive : map.get(t);
-            if (targetArchive == null) {
-                map.put(t, targetArchive = NOT_FOUND);
-            }
-
-            String origin = o.getClassName();
-            String target = t.getClassName();
-            add(origin, target, targetArchive, t.getPackageName());
+            return curDep;
         }
     }
 
-    private class PackageVisitor extends ArchiveDeps {
-        PackageVisitor(Archive archive) {
-            super(archive);
+    /*
+     * Class-level or package-level dependency
+     */
+    class Dep {
+        final String origin;
+        final Archive originArchive;
+        final String target;
+        final Archive targetArchive;
+
+        Dep(String origin, Archive originArchive, String target, Archive targetArchive) {
+            this.origin = origin;
+            this.originArchive = originArchive;
+            this.target = target;
+            this.targetArchive = targetArchive;
         }
+
+        String origin() {
+            return origin;
+        }
+
+        Archive originArchive() {
+            return originArchive;
+        }
+
+        String target() {
+            return target;
+        }
+
+        Archive targetArchive() {
+            return targetArchive;
+        }
+
         @Override
-        public void visit(Location o, Location t) {
-            Archive targetArchive =
-                this.archive.getClasses().contains(t) ? this.archive : map.get(t);
-            if (targetArchive == null) {
-                map.put(t, targetArchive = NOT_FOUND);
+        @SuppressWarnings("unchecked")
+        public boolean equals(Object o) {
+            if (o instanceof Dep) {
+                Dep d = (Dep) o;
+                return this.origin.equals(d.origin) &&
+                        this.originArchive == d.originArchive &&
+                        this.target.equals(d.target) &&
+                        this.targetArchive == d.targetArchive;
             }
+            return false;
+        }
 
-            String origin = packageOf(o);
-            String target = packageOf(t);
-            add(origin, target, targetArchive, t.getPackageName());
-        }
-        public String packageOf(Location o) {
-            String pkg = o.getPackageName();
-            return pkg.isEmpty() ? "<unnamed>" : pkg;
+        @Override
+        public int hashCode() {
+            int hash = 7;
+            hash = 67*hash + Objects.hashCode(this.origin)
+                           + Objects.hashCode(this.originArchive)
+                           + Objects.hashCode(this.target)
+                           + Objects.hashCode(this.targetArchive);
+            return hash;
         }
     }
 }
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,28 +25,34 @@
 package com.sun.tools.jdeps;
 
 import com.sun.tools.classfile.Dependency.Location;
+
+import java.io.IOException;
 import java.nio.file.Path;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Represents the source of the class files.
  */
 public class Archive {
+    public static Archive getInstance(Path p) throws IOException {
+        return new Archive(p, ClassFileReader.newInstance(p));
+    }
+
     private final Path path;
     private final String filename;
     private final ClassFileReader reader;
-    private final Map<Location, Set<Location>> deps = new HashMap<>();
+    protected Map<Location, Set<Location>> deps = new ConcurrentHashMap<>();
 
-    public Archive(String name) {
+    protected Archive(String name) {
         this.path = null;
         this.filename = name;
         this.reader = null;
     }
 
-    public Archive(Path p, ClassFileReader reader) {
+    protected Archive(Path p, ClassFileReader reader) {
         this.path = p;
         this.filename = path.getFileName().toString();
         this.reader = reader;
@@ -56,7 +62,7 @@
         return reader;
     }
 
-    public String getFileName() {
+    public String getName() {
         return filename;
     }
 
@@ -89,6 +95,10 @@
         }
     }
 
+    public boolean isEmpty() {
+        return getClasses().isEmpty();
+    }
+
     public String getPathName() {
         return path != null ? path.toString() : filename;
     }
--- a/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -68,7 +68,8 @@
 
     protected final Path path;
     protected final String baseFileName;
-    private ClassFileReader(Path path) {
+    protected final List<String> skippedEntries = new ArrayList<>();
+    protected ClassFileReader(Path path) {
         this.path = path;
         this.baseFileName = path.getFileName() != null
                                 ? path.getFileName().toString()
@@ -79,6 +80,10 @@
         return baseFileName;
     }
 
+    public List<String> skippedEntries() {
+        return skippedEntries;
+    }
+
     /**
      * Returns the ClassFile matching the given binary name
      * or a fully-qualified class name.
@@ -232,11 +237,12 @@
         }
     }
 
-    private static class JarFileReader extends ClassFileReader {
-        final JarFile jarfile;
+    static class JarFileReader extends ClassFileReader {
+        private final JarFile jarfile;
         JarFileReader(Path path) throws IOException {
-            this(path, new JarFile(path.toFile()));
+            this(path, new JarFile(path.toFile(), false));
         }
+
         JarFileReader(Path path, JarFile jf) throws IOException {
             super(path);
             this.jarfile = jf;
@@ -252,18 +258,18 @@
                             + entryName.substring(i + 1, entryName.length()));
                 }
                 if (e != null) {
-                    return readClassFile(e);
+                    return readClassFile(jarfile, e);
                 }
             } else {
                 JarEntry e = jarfile.getJarEntry(name + ".class");
                 if (e != null) {
-                    return readClassFile(e);
+                    return readClassFile(jarfile, e);
                 }
             }
             return null;
         }
 
-        private ClassFile readClassFile(JarEntry e) throws IOException {
+        protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {
             InputStream is = null;
             try {
                 is = jarfile.getInputStream(e);
@@ -277,60 +283,76 @@
         }
 
         public Iterable<ClassFile> getClassFiles() throws IOException {
-            final Iterator<ClassFile> iter = new JarFileIterator();
+            final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile);
             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;
-                    }
+    class JarFileIterator implements Iterator<ClassFile> {
+        protected final JarFileReader reader;
+        protected Enumeration<JarEntry> entries;
+        protected JarFile jf;
+        protected JarEntry nextEntry;
+        protected ClassFile cf;
+        JarFileIterator(JarFileReader reader) {
+            this(reader, null);
+        }
+        JarFileIterator(JarFileReader reader, JarFile jarfile) {
+            this.reader = reader;
+            setJarFile(jarfile);
+        }
+
+        void setJarFile(JarFile jarfile) {
+            if (jarfile == null) return;
+
+            this.jf = jarfile;
+            this.entries = jf.entries();
+            this.nextEntry = nextEntry();
+        }
+
+        public boolean hasNext() {
+            if (nextEntry != null && cf != null) {
+                return true;
+            }
+            while (nextEntry != null) {
+                try {
+                    cf = reader.readClassFile(jf, nextEntry);
+                    return true;
+                } catch (ClassFileError | IOException ex) {
+                    skippedEntries.add(nextEntry.getName());
+                }
+                nextEntry = nextEntry();
+            }
+            return false;
+        }
+
+        public ClassFile next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            ClassFile classFile = cf;
+            cf = null;
+            nextEntry = nextEntry();
+            return classFile;
+        }
+
+        protected JarEntry nextEntry() {
+            while (entries.hasMoreElements()) {
+                JarEntry e = entries.nextElement();
+                String name = e.getName();
+                if (name.endsWith(".class")) {
+                    return e;
                 }
             }
-
-            public boolean hasNext() {
-                return nextEntry != null;
-            }
-
-            public ClassFile next() {
-                if (!hasNext()) {
-                    throw new NoSuchElementException();
-                }
+            return null;
+        }
 
-                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.");
-            }
+        public void remove() {
+            throw new UnsupportedOperationException("Not supported yet.");
         }
     }
 }
--- a/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -30,7 +30,9 @@
 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 com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
+import static com.sun.tools.jdeps.Analyzer.Type.*;
 import java.io.*;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
@@ -110,7 +112,7 @@
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
                 Path p = Paths.get(arg);
                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
-                    throw new BadArgs("err.dot.output.path", arg);
+                    throw new BadArgs("err.invalid.path", arg);
                 }
                 task.options.dotOutputDir = arg;
             }
@@ -118,25 +120,26 @@
         new Option(false, "-s", "-summary") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.showSummary = true;
-                task.options.verbose = Analyzer.Type.SUMMARY;
+                task.options.verbose = SUMMARY;
             }
         },
         new Option(false, "-v", "-verbose",
                           "-verbose:package",
-                          "-verbose:class")
-        {
+                          "-verbose:class") {
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
                 switch (opt) {
                     case "-v":
                     case "-verbose":
-                        task.options.verbose = Analyzer.Type.VERBOSE;
+                        task.options.verbose = VERBOSE;
+                        task.options.filterSameArchive = false;
+                        task.options.filterSamePackage = false;
                         break;
                     case "-verbose:package":
-                            task.options.verbose = Analyzer.Type.PACKAGE;
-                            break;
+                        task.options.verbose = PACKAGE;
+                        break;
                     case "-verbose:class":
-                            task.options.verbose = Analyzer.Type.CLASS;
-                            break;
+                        task.options.verbose = CLASS;
+                        break;
                     default:
                         throw new BadArgs("err.invalid.arg.for.option", opt);
                 }
@@ -157,6 +160,32 @@
                 task.options.regex = arg;
             }
         },
+
+        new Option(true, "-f", "-filter") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.filterRegex = arg;
+            }
+        },
+        new Option(false, "-filter:package",
+                          "-filter:archive",
+                          "-filter:none") {
+            void process(JdepsTask task, String opt, String arg) {
+                switch (opt) {
+                    case "-filter:package":
+                        task.options.filterSamePackage = true;
+                        task.options.filterSameArchive = false;
+                        break;
+                    case "-filter:archive":
+                        task.options.filterSameArchive = true;
+                        task.options.filterSamePackage = false;
+                        break;
+                    case "-filter:none":
+                        task.options.filterSameArchive = false;
+                        task.options.filterSamePackage = false;
+                        break;
+                }
+            }
+        },
         new Option(true, "-include") {
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
                 task.options.includePattern = Pattern.compile(arg);
@@ -178,12 +207,15 @@
         new Option(false, "-R", "-recursive") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.depth = 0;
+                // turn off filtering
+                task.options.filterSameArchive = false;
+                task.options.filterSamePackage = false;
             }
         },
         new Option(false, "-jdkinternals") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.findJDKInternals = true;
-                task.options.verbose = Analyzer.Type.CLASS;
+                task.options.verbose = CLASS;
                 if (task.options.includePattern == null) {
                     task.options.includePattern = Pattern.compile(".*");
                 }
@@ -262,7 +294,7 @@
                 showHelp();
                 return EXIT_CMDERR;
             }
-            if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
+            if (options.showSummary && options.verbose != SUMMARY) {
                 showHelp();
                 return EXIT_CMDERR;
             }
@@ -283,9 +315,28 @@
 
     private final List<Archive> sourceLocations = new ArrayList<>();
     private boolean run() throws IOException {
+        // parse classfiles and find all dependencies
         findDependencies();
-        Analyzer analyzer = new Analyzer(options.verbose);
+
+        Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
+            @Override
+            public boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive) {
+                if (options.findJDKInternals) {
+                    // accepts target that is JDK class but not exported
+                    return isJDKArchive(targetArchive) &&
+                              !((JDKArchive) targetArchive).isExported(target.getClassName());
+                } else if (options.filterSameArchive) {
+                    // accepts origin and target that from different archive
+                    return originArchive != targetArchive;
+                }
+                return true;
+            }
+        });
+
+        // analyze the dependencies
         analyzer.run(sourceLocations);
+
+        // output result
         if (options.dotOutputDir != null) {
             Path dir = Paths.get(options.dotOutputDir);
             Files.createDirectories(dir);
@@ -296,27 +347,34 @@
         return true;
     }
 
-    private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
+    private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {
+        // If verbose mode (-v or -verbose option),
+        // the summary.dot file shows package-level dependencies.
+        Analyzer.Type summaryType =
+            (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;
         Path summary = dir.resolve("summary.dot");
-        boolean verbose = options.verbose == Analyzer.Type.VERBOSE;
-        DotGraph<?> graph = verbose ? new DotSummaryForPackage()
-                                    : new DotSummaryForArchive();
-        for (Archive archive : sourceLocations) {
-            analyzer.visitArchiveDependences(archive, graph);
-            if (verbose || options.showLabel) {
-                // traverse detailed dependences to generate package-level
-                // summary or build labels for edges
-                analyzer.visitDependences(archive, graph);
+        try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
+             SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
+            for (Archive archive : sourceLocations) {
+                if (!archive.isEmpty()) {
+                    if (options.verbose == PACKAGE || options.verbose == SUMMARY) {
+                        if (options.showLabel) {
+                            // build labels listing package-level dependencies
+                            analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
+                        }
+                    }
+                    analyzer.visitDependences(archive, dotfile, summaryType);
+                }
             }
         }
-        try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) {
-            graph.writeTo(sw);
-        }
+    }
+
+    private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
         // output individual .dot file for each archive
-        if (options.verbose != Analyzer.Type.SUMMARY) {
+        if (options.verbose != SUMMARY) {
             for (Archive archive : sourceLocations) {
                 if (analyzer.hasDependences(archive)) {
-                    Path dotfile = dir.resolve(archive.getFileName() + ".dot");
+                    Path dotfile = dir.resolve(archive.getName() + ".dot");
                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
                         analyzer.visitDependences(archive, formatter);
@@ -324,17 +382,23 @@
                 }
             }
         }
+        // generate summary dot file
+        generateSummaryDotFile(dir, analyzer);
     }
 
     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
+        RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
+        RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
         for (Archive archive : sourceLocations) {
-            RawOutputFormatter formatter = new RawOutputFormatter(writer);
-            analyzer.visitArchiveDependences(archive, formatter);
-            if (options.verbose != Analyzer.Type.SUMMARY) {
-                analyzer.visitDependences(archive, formatter);
+            if (!archive.isEmpty()) {
+                analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
+                if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {
+                    analyzer.visitDependences(archive, depFormatter);
+                }
             }
         }
     }
+
     private boolean isValidClassName(String name) {
         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
             return false;
@@ -348,21 +412,54 @@
         return true;
     }
 
-    private Dependency.Filter getDependencyFilter() {
-         if (options.regex != null) {
-            return Dependencies.getRegexFilter(Pattern.compile(options.regex));
-        } else if (options.packageNames.size() > 0) {
-            return Dependencies.getPackageFilter(options.packageNames, false);
-        } else {
-            return new Dependency.Filter() {
-                @Override
-                public boolean accepts(Dependency dependency) {
-                    return !dependency.getOrigin().equals(dependency.getTarget());
-                }
-            };
+    /*
+     * Dep Filter configured based on the input jdeps option
+     * 1. -p and -regex to match target dependencies
+     * 2. -filter:package to filter out same-package dependencies
+     *
+     * This filter is applied when jdeps parses the class files
+     * and filtered dependencies are not stored in the Analyzer.
+     *
+     * -filter:archive is applied later in the Analyzer as the
+     * containing archive of a target class may not be known until
+     * the entire archive
+     */
+    class DependencyFilter implements Dependency.Filter {
+        final Dependency.Filter filter;
+        final Pattern filterPattern;
+        DependencyFilter() {
+            if (options.regex != null) {
+                this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
+            } else if (options.packageNames.size() > 0) {
+                this.filter = Dependencies.getPackageFilter(options.packageNames, false);
+            } else {
+                this.filter = null;
+            }
+
+            this.filterPattern =
+                options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;
+        }
+        @Override
+        public boolean accepts(Dependency d) {
+            if (d.getOrigin().equals(d.getTarget())) {
+                return false;
+            }
+            String pn = d.getTarget().getPackageName();
+            if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
+                return false;
+            }
+
+            if (filterPattern != null && filterPattern.matcher(pn).matches()) {
+                return false;
+            }
+            return filter != null ? filter.accepts(d) : true;
         }
     }
 
+    /**
+     * Tests if the given class matches the pattern given in the -include option
+     * or if it's a public class if -apionly option is specified
+     */
     private boolean matches(String classname, AccessFlags flags) {
         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
             return false;
@@ -377,14 +474,14 @@
         Dependency.Finder finder =
             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
                             : Dependencies.getClassDependencyFinder();
-        Dependency.Filter filter = getDependencyFilter();
+        Dependency.Filter filter = new DependencyFilter();
 
         List<Archive> archives = new ArrayList<>();
         Deque<String> roots = new LinkedList<>();
         for (String s : classes) {
             Path p = Paths.get(s);
             if (Files.exists(p)) {
-                archives.add(new Archive(p, ClassFileReader.newInstance(p)));
+                archives.add(Archive.getInstance(p));
             } else {
                 if (isValidClassName(s)) {
                     roots.add(s);
@@ -421,19 +518,26 @@
                     throw new ClassFileError(e);
                 }
 
-                if (matches(classFileName, cf.access_flags)) {
-                    if (!doneClasses.contains(classFileName)) {
-                        doneClasses.add(classFileName);
+                // tests if this class matches the -include or -apiOnly option if specified
+                if (!matches(classFileName, cf.access_flags)) {
+                    continue;
+                }
+
+                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.addClass(d.getOrigin(), d.getTarget());
                     }
-                    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.addClass(d.getOrigin(), d.getTarget());
-                        }
-                    }
+                }
+                for (String name : a.reader().skippedEntries()) {
+                    warning("warn.skipped.entry", name, a.getPathName());
                 }
             }
         }
@@ -462,6 +566,10 @@
                             // if name is a fully-qualified class name specified
                             // from command-line, this class might already be parsed
                             doneClasses.add(classFileName);
+                            // process @jdk.Exported for JDK classes
+                            if (isJDKArchive(a)) {
+                                ((JDKArchive)a).processJdkExported(cf);
+                            }
                             for (Dependency d : finder.findDependencies(cf)) {
                                 if (depth == 0) {
                                     // ignore the dependency
@@ -544,7 +652,7 @@
         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")) {
+            if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
                 continue;
             }
             log.println(getMessage("main.opt." + name));
@@ -582,14 +690,18 @@
         boolean fullVersion;
         boolean showProfile;
         boolean showSummary;
-        boolean wildcard;
         boolean apiOnly;
         boolean showLabel;
         boolean findJDKInternals;
+        // default is to show package-level dependencies
+        // and filter references from same package
+        Analyzer.Type verbose = PACKAGE;
+        boolean filterSamePackage = true;
+        boolean filterSameArchive = false;
+        String filterRegex;
         String dotOutputDir;
         String classpath = "";
         int depth = 1;
-        Analyzer.Type verbose = Analyzer.Type.PACKAGE;
         Set<String> packageNames = new HashSet<>();
         String regex;             // apply to the dependences
         Pattern includePattern;   // apply to classes
@@ -613,19 +725,6 @@
         }
     }
 
-    private List<Archive> getArchives(List<String> filenames) throws IOException {
-        List<Archive> result = new ArrayList<>();
-        for (String s : filenames) {
-            Path p = Paths.get(s);
-            if (Files.exists(p)) {
-                result.add(new Archive(p, ClassFileReader.newInstance(p)));
-            } else {
-                warning("warn.file.not.exist", s);
-            }
-        }
-        return result;
-    }
-
     private List<Archive> getClassPathArchives(String paths) throws IOException {
         List<Archive> result = new ArrayList<>();
         if (paths.isEmpty()) {
@@ -648,7 +747,7 @@
                 }
                 for (Path f : files) {
                     if (Files.exists(f)) {
-                        result.add(new Archive(f, ClassFileReader.newInstance(f)));
+                        result.add(Archive.getInstance(f));
                     }
                 }
             }
@@ -656,81 +755,50 @@
         return result;
     }
 
-    /**
-     * If the given archive is JDK archive and non-null Profile,
-     * this method returns the profile name only if -profile option is specified;
-     * a null profile indicates it accesses a private JDK API and this method
-     * will return "JDK internal API".
-     *
-     * For non-JDK archives, this method returns the file name of the archive.
-     */
-    private String getProfileArchiveInfo(Archive source, Profile profile) {
-        if (options.showProfile && profile != null)
-            return profile.toString();
-
-        if (source instanceof JDKArchive) {
-            return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
-        }
-        return source.getFileName();
-    }
-
-    /**
-     * Returns the profile name or "JDK internal API" for JDK archive;
-     * otherwise empty string.
-     */
-    private String profileName(Archive archive, Profile profile) {
-        if (archive instanceof JDKArchive) {
-            return Objects.toString(profile, "JDK internal API");
-        } else {
-            return "";
-        }
-    }
-
     class RawOutputFormatter implements Analyzer.Visitor {
         private final PrintWriter writer;
+        private String pkg = "";
         RawOutputFormatter(PrintWriter writer) {
             this.writer = writer;
         }
-
-        private String pkg = "";
         @Override
-        public void visitDependence(String origin, Archive source,
-                                    String target, Archive archive, Profile profile) {
-            if (options.findJDKInternals &&
-                    !(archive instanceof JDKArchive && profile == null)) {
-                // filter dependences other than JDK internal APIs
-                return;
-            }
-            if (options.verbose == Analyzer.Type.VERBOSE) {
-                writer.format("   %-50s -> %-50s %s%n",
-                              origin, target, getProfileArchiveInfo(archive, profile));
+        public void visitDependence(String origin, Archive originArchive,
+                                    String target, Archive targetArchive) {
+            String tag = toTag(target, targetArchive);
+            if (options.verbose == VERBOSE) {
+                writer.format("   %-50s -> %-50s %s%n", origin, target, tag);
             } else {
                 if (!origin.equals(pkg)) {
                     pkg = origin;
-                    writer.format("   %s (%s)%n", origin, source.getFileName());
+                    writer.format("   %s (%s)%n", origin, originArchive.getName());
                 }
-                writer.format("      -> %-50s %s%n",
-                              target, getProfileArchiveInfo(archive, profile));
-            }
-        }
-
-        @Override
-        public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
-            writer.format("%s -> %s", origin.getPathName(), target.getPathName());
-            if (options.showProfile && profile != null) {
-                writer.format(" (%s)%n", profile);
-            } else {
-                writer.format("%n");
+                writer.format("      -> %-50s %s%n", target, tag);
             }
         }
     }
 
-    class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
+    class RawSummaryFormatter implements Analyzer.Visitor {
+        private final PrintWriter writer;
+        RawSummaryFormatter(PrintWriter writer) {
+            this.writer = writer;
+        }
+        @Override
+        public void visitDependence(String origin, Archive originArchive,
+                                    String target, Archive targetArchive) {
+            writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());
+            if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
+                writer.format(" (%s)", target);
+            }
+            writer.format("%n");
+        }
+    }
+
+    class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
         private final PrintWriter writer;
         private final String name;
         DotFileFormatter(PrintWriter writer, Archive archive) {
             this.writer = writer;
-            this.name = archive.getFileName();
+            this.name = archive.getName();
             writer.format("digraph \"%s\" {%n", name);
             writer.format("    // Path: %s%n", archive.getPathName());
         }
@@ -741,173 +809,123 @@
         }
 
         @Override
-        public void visitDependence(String origin, Archive source,
-                                    String target, Archive archive, Profile profile) {
-            if (options.findJDKInternals &&
-                    !(archive instanceof JDKArchive && profile == null)) {
-                // filter dependences other than JDK internal APIs
-                return;
-            }
-            // if -P option is specified, package name -> profile will
-            // be shown and filter out multiple same edges.
-            String name = getProfileArchiveInfo(archive, profile);
-            writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
-        }
-        @Override
-        public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    class DotSummaryForArchive extends DotGraph<Archive> {
-        @Override
-        public void visitDependence(String origin, Archive source,
-                                    String target, Archive archive, Profile profile) {
-            Edge e = findEdge(source, archive);
-            assert e != null;
-            // add the dependency to the label if enabled and not compact1
-            if (profile == Profile.COMPACT1) {
-                return;
-            }
-            e.addLabel(origin, target, profileName(archive, profile));
-        }
-        @Override
-        public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
-            // add an edge with the archive's name with no tag
-            // so that there is only one node for each JDK archive
-            // while there may be edges to different profiles
-            Edge e = addEdge(origin, target, "");
-            if (target instanceof JDKArchive) {
-                // add a label to print the profile
-                if (profile == null) {
-                    e.addLabel("JDK internal API");
-                } else if (options.showProfile && !options.showLabel) {
-                    e.addLabel(profile.toString());
-                }
-            }
+        public void visitDependence(String origin, Archive originArchive,
+                                    String target, Archive targetArchive) {
+            String tag = toTag(target, targetArchive);
+            writer.format("   %-50s -> \"%s\";%n",
+                          String.format("\"%s\"", origin),
+                          tag.isEmpty() ? target
+                                        : String.format("%s (%s)", target, tag));
         }
     }
 
-    // DotSummaryForPackage generates the summary.dot file for verbose mode
-    // (-v or -verbose option) that includes all class dependencies.
-    // The summary.dot file shows package-level dependencies.
-    class DotSummaryForPackage extends DotGraph<String> {
-        private String packageOf(String cn) {
-            int i = cn.lastIndexOf('.');
-            return i > 0 ? cn.substring(0, i) : "<unnamed>";
+    class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
+        private final PrintWriter writer;
+        private final Analyzer.Type type;
+        private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
+        SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
+            this.writer = writer;
+            this.type = type;
+            writer.format("digraph \"summary\" {%n");
         }
-        @Override
-        public void visitDependence(String origin, Archive source,
-                                    String target, Archive archive, Profile profile) {
-            // add a package dependency edge
-            String from = packageOf(origin);
-            String to = packageOf(target);
-            Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
 
-            // add the dependency to the label if enabled and not compact1
-            if (!options.showLabel || profile == Profile.COMPACT1) {
-                return;
-            }
-
-            // trim the package name of origin to shorten the label
-            int i = origin.lastIndexOf('.');
-            String n1 = i < 0 ? origin : origin.substring(i+1);
-            e.addLabel(n1, target, profileName(archive, profile));
-        }
         @Override
-        public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
-            // nop
-        }
-    }
-    abstract class DotGraph<T> implements Analyzer.Visitor  {
-        private final Set<Edge> edges = new LinkedHashSet<>();
-        private Edge curEdge;
-        public void writeTo(PrintWriter writer) {
-            writer.format("digraph \"summary\" {%n");
-            for (Edge e: edges) {
-                writeEdge(writer, e);
-            }
+        public void close() {
             writer.println("}");
         }
 
-        void writeEdge(PrintWriter writer, Edge e) {
-            writer.format("   %-50s -> \"%s\"%s;%n",
-                          String.format("\"%s\"", e.from.toString()),
-                          e.tag.isEmpty() ? e.to
-                                          : String.format("%s (%s)", e.to, e.tag),
-                          getLabel(e));
+        @Override
+        public void visitDependence(String origin, Archive originArchive,
+                                    String target, Archive targetArchive) {
+            String targetName = type == PACKAGE ? target : targetArchive.getName();
+            if (type == PACKAGE) {
+                String tag = toTag(target, targetArchive, type);
+                if (!tag.isEmpty())
+                    targetName += " (" + tag + ")";
+            } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
+                targetName += " (" + target + ")";
+            }
+            String label = getLabel(originArchive, targetArchive);
+            writer.format("  %-50s -> \"%s\"%s;%n",
+                          String.format("\"%s\"", origin), targetName, label);
         }
 
-        Edge addEdge(T origin, T target, String tag) {
-            Edge e = new Edge(origin, target, tag);
-            if (e.equals(curEdge)) {
-                return curEdge;
-            }
+        String getLabel(Archive origin, Archive target) {
+            if (edges.isEmpty())
+                return "";
 
-            if (edges.contains(e)) {
-                for (Edge e1 : edges) {
-                   if (e.equals(e1)) {
-                       curEdge = e1;
-                   }
-                }
-            } else {
-                edges.add(e);
-                curEdge = e;
-            }
-            return curEdge;
-        }
-
-        Edge findEdge(T origin, T target) {
-            for (Edge e : edges) {
-                if (e.from.equals(origin) && e.to.equals(target)) {
-                    return e;
-                }
-            }
-            return null;
-        }
-
-        String getLabel(Edge e) {
-            String label = e.label.toString();
-            return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
+            StringBuilder label = edges.get(origin).get(target);
+            return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
         }
 
-        class Edge {
-            final T from;
-            final T to;
-            final String tag;  // optional tag
-            final StringBuilder label = new StringBuilder();
-            Edge(T from, T to, String tag) {
-                this.from = from;
-                this.to = to;
-                this.tag = tag;
-            }
-            void addLabel(String s) {
-                label.append(s).append("\\n");
-            }
-            void addLabel(String origin, String target, String profile) {
-                label.append(origin).append(" -> ").append(target);
-                if (!profile.isEmpty()) {
-                    label.append(" (" + profile + ")");
+        Analyzer.Visitor labelBuilder() {
+            // show the package-level dependencies as labels in the dot graph
+            return new Analyzer.Visitor() {
+                @Override
+                public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
+                    edges.putIfAbsent(originArchive, new HashMap<>());
+                    edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
+                    StringBuilder sb = edges.get(originArchive).get(targetArchive);
+                    String tag = toTag(target, targetArchive, PACKAGE);
+                    addLabel(sb, origin, target, tag);
                 }
-                label.append("\\n");
-            }
-            @Override @SuppressWarnings("unchecked")
-            public boolean equals(Object o) {
-                if (o instanceof DotGraph<?>.Edge) {
-                    DotGraph<?>.Edge e = (DotGraph<?>.Edge)o;
-                    return this.from.equals(e.from) &&
-                           this.to.equals(e.to) &&
-                           this.tag.equals(e.tag);
+
+                void addLabel(StringBuilder label, String origin, String target, String tag) {
+                    label.append(origin).append(" -> ").append(target);
+                    if (!tag.isEmpty()) {
+                        label.append(" (" + tag + ")");
+                    }
+                    label.append("\\n");
                 }
-                return false;
-            }
-            @Override
-            public int hashCode() {
-                int hash = 7;
-                hash = 67 * hash + Objects.hashCode(this.from) +
-                       Objects.hashCode(this.to) + Objects.hashCode(this.tag);
-                return hash;
-            }
+            };
         }
     }
+
+    /**
+     * Test if the given archive is part of the JDK
+     */
+    private boolean isJDKArchive(Archive archive) {
+        return JDKArchive.class.isInstance(archive);
+    }
+
+    /**
+     * If the given archive is JDK archive, this method returns the profile name
+     * only if -profile option is specified; it accesses a private JDK API and
+     * the returned value will have "JDK internal API" prefix
+     *
+     * For non-JDK archives, this method returns the file name of the archive.
+     */
+    private String toTag(String name, Archive source, Analyzer.Type type) {
+        if (!isJDKArchive(source)) {
+            return source.getName();
+        }
+
+        JDKArchive jdk = (JDKArchive)source;
+        boolean isExported = false;
+        if (type == CLASS || type == VERBOSE) {
+            isExported = jdk.isExported(name);
+        } else {
+            isExported = jdk.isExportedPackage(name);
+        }
+        Profile p = getProfile(name, type);
+        if (isExported) {
+            // exported API
+            return options.showProfile && p != null ? p.profileName() : "";
+        } else {
+            return "JDK internal API (" + source.getName() + ")";
+        }
+    }
+
+    private String toTag(String name, Archive source) {
+        return toTag(name, source, options.verbose);
+    }
+
+    private Profile getProfile(String name, Analyzer.Type type) {
+        String pn = name;
+        if (type == CLASS || type == VERBOSE) {
+            int i = name.lastIndexOf('.');
+            pn = i > 0 ? name.substring(0, i) : "";
+        }
+        return Profile.getProfile(pn);
+    }
 }
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Main.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Main.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -63,4 +63,3 @@
         return t.run(args);
     }
 }
-
--- a/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,12 @@
  */
 package com.sun.tools.jdeps;
 
+import com.sun.tools.classfile.Annotation;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.ConstantPool;
+import com.sun.tools.classfile.ConstantPoolException;
+import com.sun.tools.classfile.RuntimeAnnotations_attribute;
+import com.sun.tools.classfile.Dependencies.ClassFileError;
 import java.io.IOException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
@@ -33,11 +39,15 @@
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.*;
 
+import static com.sun.tools.classfile.Attribute.*;
+
 /**
  * ClassPath for Java SE and JDK
  */
 class PlatformClassPath {
-    private final static List<Archive> javaHomeArchives = init();
+    private static final List<String> NON_PLATFORM_JARFILES =
+        Arrays.asList("alt-rt.jar", "jfxrt.jar", "ant-javafx.jar", "javafx-mx.jar");
+    private static final List<Archive> javaHomeArchives = init();
 
     static List<Archive> getArchives() {
         return javaHomeArchives;
@@ -50,12 +60,19 @@
             if (home.endsWith("jre")) {
                 // jar files in <javahome>/jre/lib
                 result.addAll(addJarFiles(home.resolve("lib")));
+                if (home.getParent() != null) {
+                    // add tools.jar and other JDK jar files
+                    Path lib = home.getParent().resolve("lib");
+                    if (Files.exists(lib)) {
+                        result.addAll(addJarFiles(lib));
+                    }
+                }
             } else if (Files.exists(home.resolve("lib"))) {
                 // either a JRE or a jdk build image
                 Path classes = home.resolve("classes");
                 if (Files.isDirectory(classes)) {
                     // jdk build outputdir
-                    result.add(new JDKArchive(classes, ClassFileReader.newInstance(classes)));
+                    result.add(new JDKArchive(classes));
                 }
                 // add other JAR files
                 result.addAll(addJarFiles(home.resolve("lib")));
@@ -91,9 +108,9 @@
                 if (fn.endsWith(".jar")) {
                     // JDK may cobundle with JavaFX that doesn't belong to any profile
                     // Treat jfxrt.jar as regular Archive
-                    result.add(fn.equals("jfxrt.jar")
-                        ? new Archive(p, ClassFileReader.newInstance(p))
-                        : new JDKArchive(p, ClassFileReader.newInstance(p)));
+                    result.add(NON_PLATFORM_JARFILES.contains(fn)
+                                   ? Archive.getInstance(p)
+                                   : new JDKArchive(p));
                 }
                 return FileVisitResult.CONTINUE;
             }
@@ -106,8 +123,91 @@
      * or implementation classes (i.e. JDK internal API)
      */
     static class JDKArchive extends Archive {
-        JDKArchive(Path p, ClassFileReader reader) {
-            super(p, reader);
+        private static List<String> PROFILE_JARS = Arrays.asList("rt.jar", "jce.jar");
+        public static boolean isProfileArchive(Archive archive) {
+            if (archive instanceof JDKArchive) {
+                return PROFILE_JARS.contains(archive.getName());
+            }
+            return false;
+        }
+
+        private final Map<String,Boolean> exportedPackages = new HashMap<>();
+        private final Map<String,Boolean> exportedTypes = new HashMap<>();
+        JDKArchive(Path p) throws IOException {
+            super(p, ClassFileReader.newInstance(p));
+        }
+
+        /**
+         * Tests if a given fully-qualified name is an exported type.
+         */
+        public boolean isExported(String cn) {
+            int i = cn.lastIndexOf('.');
+            String pn = i > 0 ? cn.substring(0, i) : "";
+
+            boolean isJdkExported = isExportedPackage(pn);
+            if (exportedTypes.containsKey(cn)) {
+                return exportedTypes.get(cn);
+            }
+            return isJdkExported;
+        }
+
+        /**
+         * Tests if a given package name is exported.
+         */
+        public boolean isExportedPackage(String pn) {
+            if (Profile.getProfile(pn) != null) {
+                return true;
+            }
+            return exportedPackages.containsKey(pn) ? exportedPackages.get(pn) : false;
+        }
+
+        private static final String JDK_EXPORTED_ANNOTATION = "Ljdk/Exported;";
+        private Boolean isJdkExported(ClassFile cf) throws ConstantPoolException {
+            RuntimeAnnotations_attribute attr = (RuntimeAnnotations_attribute)
+                    cf.attributes.get(RuntimeVisibleAnnotations);
+            if (attr != null) {
+                for (int i = 0; i < attr.annotations.length; i++) {
+                    Annotation ann = attr.annotations[i];
+                    String annType = cf.constant_pool.getUTF8Value(ann.type_index);
+                    if (JDK_EXPORTED_ANNOTATION.equals(annType)) {
+                        boolean isJdkExported = true;
+                        for (int j = 0; j < ann.num_element_value_pairs; j++) {
+                            Annotation.element_value_pair pair = ann.element_value_pairs[j];
+                            Annotation.Primitive_element_value ev = (Annotation.Primitive_element_value) pair.value;
+                            ConstantPool.CONSTANT_Integer_info info = (ConstantPool.CONSTANT_Integer_info)
+                                    cf.constant_pool.get(ev.const_value_index);
+                            isJdkExported = info.value != 0;
+                        }
+                        return Boolean.valueOf(isJdkExported);
+                    }
+                }
+            }
+            return null;
+        }
+
+        void processJdkExported(ClassFile cf) throws IOException {
+            try {
+                String cn = cf.getName();
+                String pn = cn.substring(0, cn.lastIndexOf('/')).replace('/', '.');
+
+                Boolean b = isJdkExported(cf);
+                if (b != null) {
+                    exportedTypes.put(cn.replace('/', '.'), b);
+                }
+                if (!exportedPackages.containsKey(pn)) {
+                    // check if package-info.class has @jdk.Exported
+                    Boolean isJdkExported = null;
+                    ClassFile pcf = reader().getClassFile(cn.substring(0, cn.lastIndexOf('/')+1) + "package-info");
+                    if (pcf != null) {
+                        isJdkExported = isJdkExported(pcf);
+                    }
+                    if (isJdkExported != null) {
+                        exportedPackages.put(pn, isJdkExported);
+                    }
+                }
+            } catch (ConstantPoolException e) {
+                throw new ClassFileError(e);
+            }
         }
     }
 }
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Profile.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Profile.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -43,7 +43,6 @@
  * Build the profile information from ct.sym if exists.
  */
 enum Profile {
-
     COMPACT1("compact1", 1),
     COMPACT2("compact2", 2),
     COMPACT3("compact3", 3),
@@ -61,8 +60,7 @@
         this.proprietaryPkgs = new HashSet<>();
     }
 
-    @Override
-    public String toString() {
+    public String profileName() {
         return name;
     }
 
@@ -77,7 +75,7 @@
     public static Profile getProfile(String pn) {
         Profile profile = PackageToProfile.map.get(pn);
         return (profile != null && profile.packages.contains(pn))
-                ? profile : null;
+                    ? profile : null;
     }
 
     static class PackageToProfile {
--- a/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Tue Jul 08 18:26:34 2014 -0700
@@ -1,6 +1,6 @@
 main.usage.summary=\
 Usage: {0} <options> <classes...>\n\
-use -h, -? or --help for a list of possible options
+use -h, -? or -help for a list of possible options
 
 main.usage=\
 Usage: {0} <options> <classes...>\n\
@@ -18,20 +18,29 @@
 
 main.opt.v=\
 \  -v           -verbose              Print all class level dependencies\n\
+\                                     Equivalent to -verbose:class -filter:none.\n\
 \  -verbose:package                   Print package-level dependencies excluding\n\
-\                                     dependencies within the same archive\n\
+\                                     dependencies within the same package by default\n\
 \  -verbose:class                     Print class-level dependencies excluding\n\
-\                                     dependencies within the same archive
+\                                     dependencies within the same package by default
+
+main.opt.f=\
+\  -f <regex>   -filter <regex>       Filter dependences matching the given pattern\n\
+\                                     If given multiple times, the last one will be used.\n\
+\  -filter:package                    Filter dependences within the same package (default)\n\
+\  -filter:archive                    Filter dependences within the same archive\n\
+\  -filter:none                       No -filter:package and -filter:archive filtering\n\
+\                                     Filtering specified via the -filter option still applies.
 
 main.opt.s=\
 \  -s           -summary              Print dependency summary only
 
 main.opt.p=\
-\  -p <pkgname> -package <pkgname>    Finds dependences in the given package\n\
+\  -p <pkgname> -package <pkgname>    Finds dependences matching the given package name\n\
 \                                     (may be given multiple times)
 
 main.opt.e=\
-\  -e <regex>   -regex <regex>        Finds dependences in packages matching pattern\n\
+\  -e <regex>   -regex <regex>        Finds dependences matching the given pattern\n\
 \                                     (-p and -e are exclusive)
 
 main.opt.include=\
@@ -47,7 +56,10 @@
 \  -cp <path>   -classpath <path>     Specify where to find class files
 
 main.opt.R=\
-\  -R           -recursive            Recursively traverse all dependencies
+\  -R           -recursive            Recursively traverse all dependencies.\n\
+\                                     The -R option implies -filter:none.  If -p, -e, -f\n\
+\                                     option is specified, only the matching dependences\n\
+\                                     are analyzed.
 
 main.opt.apionly=\
 \  -apionly                           Restrict analysis to APIs i.e. dependences\n\
@@ -74,12 +86,11 @@
 
 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}
 err.option.unsupported={0} not supported: {1}
 err.profiles.msg=No profile information
-err.dot.output.path=invalid path: {0}
+err.invalid.path=invalid path: {0}
 warn.invalid.arg=Invalid classname or pathname not exist: {0}
 warn.split.package=package {0} defined in {1} {2}
 
--- a/langtools/test/tools/jdeps/APIDeps.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/APIDeps.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8015912 8029216
+ * @bug 8015912 8029216 8048063
  * @summary Test -apionly and -jdkinternals options
  * @build m.Bar m.Foo m.Gee b.B c.C c.I d.D e.E f.F g.G
  * @run main APIDeps
@@ -81,27 +81,39 @@
              new String[] {"compact1", "compact3", testDirBasename},
              new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"});
         test(new File(mDir, "Foo.class"),
+             new String[] {"c.I", "e.E", "f.F"},
+             new String[] {testDirBasename},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-P"});
+        test(new File(mDir, "Foo.class"),
              new String[] {"c.I", "e.E", "f.F", "m.Bar"},
              new String[] {testDirBasename},
-             new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"});
+             new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-filter:none", "-P"});
         test(new File(mDir, "Gee.class"),
-             new String[] {"g.G", "sun.misc.Lock"},
-             new String[] {testDirBasename, "JDK internal API"},
-             new String[] {"-classpath", testDir.getPath(), "-verbose"});
+             new String[] {"g.G", "sun.misc.Lock", "com.sun.tools.classfile.ClassFile",
+                           "com.sun.management.ThreadMXBean", "com.sun.source.tree.BinaryTree"},
+             new String[] {testDirBasename, "JDK internal API", "compact3", ""},
+             new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"});
 
         // -jdkinternals
         test(new File(mDir, "Gee.class"),
-             new String[] {"sun.misc.Lock"},
+             new String[] {"sun.misc.Lock", "com.sun.tools.classfile.ClassFile"},
              new String[] {"JDK internal API"},
              new String[] {"-jdkinternals"});
         // -jdkinternals parses all classes on -classpath and the input arguments
         test(new File(mDir, "Gee.class"),
-             new String[] {"sun.misc.Lock", "sun.misc.Unsafe"},
+             new String[] {"com.sun.tools.jdeps.Main", "com.sun.tools.classfile.ClassFile",
+                           "sun.misc.Lock", "sun.misc.Unsafe"},
              new String[] {"JDK internal API"},
              new String[] {"-classpath", testDir.getPath(), "-jdkinternals"});
 
         // parse only APIs
-        // parse only APIs
+        test(mDir,
+             new String[] {"java.lang.Object", "java.lang.String",
+                           "java.util.Set",
+                           "c.C", "d.D", "c.I", "e.E"},
+             new String[] {"compact1", testDirBasename},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-P", "-apionly"});
+
         test(mDir,
              new String[] {"java.lang.Object", "java.lang.String",
                            "java.util.Set",
--- a/langtools/test/tools/jdeps/Basic.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/Basic.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8003562 8005428 8015912 8027481
+ * @bug 8003562 8005428 8015912 8027481 8048063
  * @summary Basic tests for jdeps tool
  * @build Test p.Foo p.Bar javax.activity.NotCompactProfile
  * @run main Basic
@@ -86,6 +86,16 @@
              new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
              new String[] {"compact1", "compact1", "not found", "not found"},
              new String[] {"-verbose:class"});
+        // test -filter:none option
+        test(new File(testDir, "p"),
+             new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto", "p"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1", "p"},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:none"});
+        // test -filter:archive option
+        test(new File(testDir, "p"),
+             new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:archive"});
         // test -p option
         test(new File(testDir, "Test.class"),
              new String[] {"p.Foo", "p.Bar"},
@@ -100,11 +110,12 @@
              new String[] {"java.lang"},
              new String[] {"compact1"},
              new String[] {"-verbose:package", "-e", "java\\.lang\\..*"});
+
         // test -classpath and -include options
         test(null,
-             new String[] {"java.lang", "java.util",
-                           "java.lang.management", "javax.crypto"},
-             new String[] {"compact1", "compact1", "compact3", "compact1"},
+             new String[] {"java.lang", "java.util", "java.lang.management",
+                           "javax.activity", "javax.crypto"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
              new String[] {"-classpath", testDir.getPath(), "-include", "p.+|Test.class"});
         test(new File(testDir, "Test.class"),
              new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/DotFileTest.java	Tue Jul 08 18:26:34 2014 -0700
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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 -dotoutput option
+ * @build Test p.Foo p.Bar javax.activity.NotCompactProfile
+ * @run main DotFileTest
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.regex.*;
+
+public class DotFileTest {
+    private static boolean symbolFileExist = initProfiles();
+    private static boolean initProfiles() {
+        // check if ct.sym exists; if not use the profiles.properties file
+        Path home = Paths.get(System.getProperty("java.home"));
+        if (home.endsWith("jre")) {
+            home = home.getParent();
+        }
+        Path ctsym = home.resolve("lib").resolve("ct.sym");
+        boolean symbolExists = ctsym.toFile().exists();
+        if (!symbolExists) {
+            Path testSrcProfiles =
+                Paths.get(System.getProperty("test.src", "."), "profiles.properties");
+            if (!testSrcProfiles.toFile().exists())
+                throw new Error(testSrcProfiles + " does not exist");
+            System.out.format("%s doesn't exist.%nUse %s to initialize profiles info%n",
+                ctsym, testSrcProfiles);
+            System.setProperty("jdeps.profiles", testSrcProfiles.toString());
+        }
+        return symbolExists;
+    }
+
+    public static void main(String... args) throws Exception {
+        int errors = 0;
+        errors += new DotFileTest().run();
+        if (errors > 0)
+            throw new Exception(errors + " errors found");
+    }
+
+    final Path dir;
+    final Path dotoutput;
+    DotFileTest() {
+        this.dir = Paths.get(System.getProperty("test.classes", "."));
+        this.dotoutput = dir.resolve("dots");
+    }
+
+    int run() throws IOException {
+        File testDir = dir.toFile();
+        // test a .class file
+        test(new File(testDir, "Test.class"),
+             new String[] {"java.lang", "p"},
+             new String[] {"compact1", "not found"});
+        // test a directory
+        // also test non-SE javax.activity class dependency
+        test(new File(testDir, "p"),
+             new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+             new String[] {"-classpath", testDir.getPath()});
+        // test class-level dependency output
+        test(new File(testDir, "Test.class"),
+             new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
+             new String[] {"compact1", "compact1", "not found", "not found"},
+             new String[] {"-verbose:class"});
+        // test -filter:none option
+        test(new File(testDir, "p"),
+             new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto", "p"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1", "p"},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:none"});
+        // test -filter:archive option
+        test(new File(testDir, "p"),
+             new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+             new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+             new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:archive"});
+        // test -p option
+        test(new File(testDir, "Test.class"),
+             new String[] {"p.Foo", "p.Bar"},
+             new String[] {"not found", "not found"},
+             new String[] {"-verbose:class", "-p", "p"});
+        // test -e option
+        test(new File(testDir, "Test.class"),
+             new String[] {"p.Foo", "p.Bar"},
+             new String[] {"not found", "not found"},
+             new String[] {"-verbose:class", "-e", "p\\..*"});
+        test(new File(testDir, "Test.class"),
+             new String[] {"java.lang"},
+             new String[] {"compact1"},
+             new String[] {"-verbose:package", "-e", "java\\.lang\\..*"});
+        // test -classpath options
+        test(new File(testDir, "Test.class"),
+             new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
+             new String[] {"compact1", "compact1", testDir.getName(), testDir.getName()},
+             new String[] {"-v", "-classpath", testDir.getPath()});
+
+        testSummary(new File(testDir, "Test.class"),
+             new String[] {"rt.jar", testDir.getName()},
+             new String[] {"compact1", ""},
+             new String[] {"-classpath", testDir.getPath()});
+        testSummary(new File(testDir, "Test.class"),
+             new String[] {"java.lang", "p"},
+             new String[] {"compact1", testDir.getName()},
+             new String[] {"-v", "-classpath", testDir.getPath()});
+        return errors;
+    }
+
+    void test(File file, String[] expect, String[] profiles) throws IOException {
+        test(file, expect, profiles, new String[0]);
+    }
+
+    void test(File file, String[] expect, String[] profiles, String[] options)
+        throws IOException
+    {
+        Path dotfile = dotoutput.resolve(file.toPath().getFileName().toString() + ".dot");
+
+        List<String> args = new ArrayList<>(Arrays.asList(options));
+        args.add("-dotoutput");
+        args.add(dotoutput.toString());
+        if (file != null) {
+            args.add(file.getPath());
+        }
+
+        Map<String,String> result = jdeps(args, dotfile);
+        checkResult("dependencies", expect, result.keySet());
+
+        // with -P option
+        List<String> argsWithDashP = new ArrayList<>();
+        argsWithDashP.add("-dotoutput");
+        argsWithDashP.add(dotoutput.toString());
+        argsWithDashP.add("-P");
+        argsWithDashP.addAll(args);
+
+        result = jdeps(argsWithDashP, dotfile);
+        checkResult("profiles", expect, profiles, result);
+    }
+
+    void testSummary(File file, String[] expect, String[] profiles, String[] options)
+        throws IOException
+    {
+        Path dotfile = dotoutput.resolve("summary.dot");
+
+        List<String> args = new ArrayList<>(Arrays.asList(options));
+        args.add("-dotoutput");
+        args.add(dotoutput.toString());
+        if (file != null) {
+            args.add(file.getPath());
+        }
+
+        Map<String,String> result = jdeps(args, dotfile);
+        checkResult("dependencies", expect, result.keySet());
+
+        // with -P option
+        List<String> argsWithDashP = new ArrayList<>();
+        argsWithDashP.add("-dotoutput");
+        argsWithDashP.add(dotoutput.toString());
+        argsWithDashP.add("-P");
+        argsWithDashP.addAll(args);
+
+        result = jdeps(argsWithDashP, dotfile);
+        checkResult("profiles", expect, profiles, result);
+    }
+
+    Map<String,String> jdeps(List<String> args, Path dotfile) throws IOException {
+        if (Files.exists(dotoutput)) {
+            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dotoutput)) {
+                for (Path p : stream) {
+                    Files.delete(p);
+                }
+            }
+            Files.delete(dotoutput);
+        }
+        // invoke jdeps
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        System.err.println("jdeps " + args);
+        int rc = com.sun.tools.jdeps.Main.run(args.toArray(new String[0]), pw);
+        pw.close();
+        String out = sw.toString();
+        if (!out.isEmpty())
+            System.err.println(out);
+        if (rc != 0)
+            throw new Error("jdeps failed: rc=" + rc);
+
+        // check output files
+        if (Files.notExists(dotfile)) {
+            throw new RuntimeException(dotfile + " doesn't exist");
+        }
+        return parse(dotfile);
+    }
+    private static Pattern pattern = Pattern.compile("(.*) -> +([^ ]*) (.*)");
+    private Map<String,String> parse(Path outfile) throws IOException {
+        Map<String,String> result = new LinkedHashMap<>();
+        for (String line : Files.readAllLines(outfile)) {
+            line = line.replace('"', ' ').replace(';', ' ');
+            Matcher pm = pattern.matcher(line);
+            if (pm.find()) {
+                String origin = pm.group(1).trim();
+                String target = pm.group(2).trim();
+                String module = pm.group(3).replace('(', ' ').replace(')', ' ').trim();
+                result.put(target, module);
+            }
+        }
+        return result;
+    }
+
+    void checkResult(String label, String[] expect, Collection<String> found) {
+        List<String> list = Arrays.asList(expect);
+        if (!isEqual(list, found))
+            error("Unexpected " + label + " found: '" + found + "', expected: '" + list + "'");
+    }
+
+    void checkResult(String label, String[] expect, String[] profiles, Map<String,String> result) {
+        if (expect.length != profiles.length)
+            error("Invalid expected names and profiles");
+
+        // check the dependencies
+        checkResult(label, expect, result.keySet());
+        // check profile information
+        checkResult(label, profiles, result.values());
+        for (int i=0; i < expect.length; i++) {
+            String profile = result.get(expect[i]);
+            if (!profile.equals(profiles[i]))
+                error("Unexpected profile: '" + profile + "', expected: '" + profiles[i] + "'");
+        }
+    }
+
+    boolean isEqual(List<String> expected, Collection<String> found) {
+        if (expected.size() != found.size())
+            return false;
+
+        List<String> list = new ArrayList<>(found);
+        list.removeAll(expected);
+        return list.isEmpty();
+    }
+
+    void error(String msg) {
+        System.err.println("Error: " + msg);
+        errors++;
+    }
+
+    int errors;
+}
--- a/langtools/test/tools/jdeps/m/Gee.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/m/Gee.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,5 +26,7 @@
 
 class Gee extends g.G {
     public sun.misc.Lock lock;
+    public com.sun.tools.classfile.ClassFile cf;     // @jdk.Exported(false)
+    public com.sun.source.tree.BinaryTree tree;      // @jdk.Exported
+    public com.sun.management.ThreadMXBean mxbean;   // @jdk.Exported on package-info
 }
-
--- a/langtools/test/tools/jdeps/p/Bar.java	Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/p/Bar.java	Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -30,4 +30,8 @@
     public javax.crypto.Cipher getCiper() {
         return null;
     }
+
+    public Foo foo() {
+        return new Foo();
+    }
 }