langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java
changeset 36526 3b41f1c69604
parent 34752 9c262a013456
child 38524 badd925c1d2f
equal deleted inserted replaced
36525:4caf88912b7f 36526:3b41f1c69604
    24  */
    24  */
    25 
    25 
    26 package com.sun.tools.jdeps;
    26 package com.sun.tools.jdeps;
    27 
    27 
    28 import java.io.PrintStream;
    28 import java.io.PrintStream;
       
    29 import java.util.ArrayList;
       
    30 import java.util.Collections;
    29 import java.util.Comparator;
    31 import java.util.Comparator;
       
    32 import java.util.Deque;
    30 import java.util.HashMap;
    33 import java.util.HashMap;
    31 import java.util.HashSet;
    34 import java.util.HashSet;
       
    35 import java.util.LinkedList;
    32 import java.util.List;
    36 import java.util.List;
    33 import java.util.Map;
    37 import java.util.Map;
    34 import java.util.Objects;
    38 import java.util.Objects;
    35 import java.util.Set;
    39 import java.util.Set;
    36 import java.util.stream.Collectors;
    40 import java.util.stream.Collectors;
    61         boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
    65         boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
    62     }
    66     }
    63 
    67 
    64     protected final Type type;
    68     protected final Type type;
    65     protected final Filter filter;
    69     protected final Filter filter;
    66     protected final Map<Archive, ArchiveDeps> results = new HashMap<>();
    70     protected final Map<Archive, Dependences> results = new HashMap<>();
    67     protected final Map<Location, Archive> map = new HashMap<>();
    71     protected final Map<Location, Archive> locationToArchive = new HashMap<>();
    68     private static final Archive NOT_FOUND
    72     static final Archive NOT_FOUND
    69         = new Archive(JdepsTask.getMessage("artifact.not.found"));
    73         = new Archive(JdepsTask.getMessage("artifact.not.found"));
    70 
    74 
    71     /**
    75     /**
    72      * Constructs an Analyzer instance.
    76      * Constructs an Analyzer instance.
    73      *
    77      *
    80     }
    84     }
    81 
    85 
    82     /**
    86     /**
    83      * Performs the dependency analysis on the given archives.
    87      * Performs the dependency analysis on the given archives.
    84      */
    88      */
    85     public boolean run(List<Archive> archives) {
    89     public boolean run(Stream<? extends Archive> archives) {
       
    90         return run(archives.collect(Collectors.toList()));
       
    91     }
       
    92 
       
    93     /**
       
    94      * Performs the dependency analysis on the given archives.
       
    95      */
       
    96     public boolean run(Iterable<? extends Archive> archives) {
    86         // build a map from Location to Archive
    97         // build a map from Location to Archive
    87         buildLocationArchiveMap(archives);
    98         buildLocationArchiveMap(archives);
    88 
    99 
    89         // traverse and analyze all dependencies
   100         // traverse and analyze all dependencies
    90         for (Archive archive : archives) {
   101         for (Archive archive : archives) {
    91             ArchiveDeps deps = new ArchiveDeps(archive, type);
   102             Dependences deps = new Dependences(archive, type);
    92             archive.visitDependences(deps);
   103             archive.visitDependences(deps);
    93             results.put(archive, deps);
   104             results.put(archive, deps);
    94         }
   105         }
    95         return true;
   106         return true;
    96     }
   107     }
    97 
   108 
    98     protected void buildLocationArchiveMap(List<Archive> archives) {
   109     protected void buildLocationArchiveMap(Iterable<? extends Archive> archives) {
    99         // build a map from Location to Archive
   110         // build a map from Location to Archive
   100         for (Archive archive: archives) {
   111         for (Archive archive: archives) {
   101             for (Location l: archive.getClasses()) {
   112             archive.getClasses()
   102                 if (!map.containsKey(l)) {
   113                    .forEach(l -> locationToArchive.putIfAbsent(l, archive));
   103                     map.put(l, archive);
       
   104                 } else {
       
   105                     // duplicated class warning?
       
   106                 }
       
   107             }
       
   108         }
   114         }
   109     }
   115     }
   110 
   116 
   111     public boolean hasDependences(Archive archive) {
   117     public boolean hasDependences(Archive archive) {
   112         if (results.containsKey(archive)) {
   118         if (results.containsKey(archive)) {
   114         }
   120         }
   115         return false;
   121         return false;
   116     }
   122     }
   117 
   123 
   118     public Set<String> dependences(Archive source) {
   124     public Set<String> dependences(Archive source) {
   119         ArchiveDeps result = results.get(source);
   125         if (!results.containsKey(source)) {
       
   126             return Collections.emptySet();
       
   127         }
       
   128         Dependences result = results.get(source);
   120         return result.dependencies().stream()
   129         return result.dependencies().stream()
   121                      .map(Dep::target)
   130                      .map(Dep::target)
   122                      .collect(Collectors.toSet());
   131                      .collect(Collectors.toSet());
       
   132     }
       
   133 
       
   134     public Stream<Archive> requires(Archive source) {
       
   135         if (!results.containsKey(source)) {
       
   136             return Stream.empty();
       
   137         }
       
   138         Dependences result = results.get(source);
       
   139         return result.requires().stream().filter(a -> !a.isEmpty());
   123     }
   140     }
   124 
   141 
   125     public interface Visitor {
   142     public interface Visitor {
   126         /**
   143         /**
   127          * Visits a recorded dependency from origin to target which can be
   144          * Visits a recorded dependency from origin to target which can be
   136      * Visit the dependencies of the given source.
   153      * Visit the dependencies of the given source.
   137      * If the requested level is SUMMARY, it will visit the required archives list.
   154      * If the requested level is SUMMARY, it will visit the required archives list.
   138      */
   155      */
   139     public void visitDependences(Archive source, Visitor v, Type level) {
   156     public void visitDependences(Archive source, Visitor v, Type level) {
   140         if (level == Type.SUMMARY) {
   157         if (level == Type.SUMMARY) {
   141             final ArchiveDeps result = results.get(source);
   158             final Dependences result = results.get(source);
   142             final Set<Archive> reqs = result.requires();
   159             final Set<Archive> reqs = result.requires();
   143             Stream<Archive> stream = reqs.stream();
   160             Stream<Archive> stream = reqs.stream();
   144             if (reqs.isEmpty()) {
   161             if (reqs.isEmpty()) {
   145                 if (hasDependences(source)) {
   162                 if (hasDependences(source)) {
   146                     // If reqs.isEmpty() and we have dependences, then it means
   163                     // If reqs.isEmpty() and we have dependences, then it means
   150             }
   167             }
   151             stream.sorted(Comparator.comparing(Archive::getName))
   168             stream.sorted(Comparator.comparing(Archive::getName))
   152                   .forEach(archive -> {
   169                   .forEach(archive -> {
   153                       Profile profile = result.getTargetProfile(archive);
   170                       Profile profile = result.getTargetProfile(archive);
   154                       v.visitDependence(source.getName(), source,
   171                       v.visitDependence(source.getName(), source,
   155                                         profile != null ? profile.profileName() : archive.getName(), archive);
   172                                         profile != null ? profile.profileName()
       
   173                                                         : archive.getName(), archive);
   156                   });
   174                   });
   157         } else {
   175         } else {
   158             ArchiveDeps result = results.get(source);
   176             Dependences result = results.get(source);
   159             if (level != type) {
   177             if (level != type) {
   160                 // requesting different level of analysis
   178                 // requesting different level of analysis
   161                 result = new ArchiveDeps(source, level);
   179                 result = new Dependences(source, level);
   162                 source.visitDependences(result);
   180                 source.visitDependences(result);
   163             }
   181             }
   164             result.dependencies().stream()
   182             result.dependencies().stream()
   165                   .sorted(Comparator.comparing(Dep::origin)
   183                   .sorted(Comparator.comparing(Dep::origin)
   166                                     .thenComparing(Dep::target))
   184                                     .thenComparing(Dep::target))
   167                   .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive()));
   185                   .forEach(d -> v.visitDependence(d.origin(), d.originArchive(),
       
   186                                                   d.target(), d.targetArchive()));
   168         }
   187         }
   169     }
   188     }
   170 
   189 
   171     public void visitDependences(Archive source, Visitor v) {
   190     public void visitDependences(Archive source, Visitor v) {
   172         visitDependences(source, v, type);
   191         visitDependences(source, v, type);
   173     }
   192     }
   174 
   193 
   175     /**
   194     /**
   176      * ArchiveDeps contains the dependencies for an Archive that can have one or
   195      * Dependences contains the dependencies for an Archive that can have one or
   177      * more classes.
   196      * more classes.
   178      */
   197      */
   179     class ArchiveDeps implements Archive.Visitor {
   198     class Dependences implements Archive.Visitor {
   180         protected final Archive archive;
   199         protected final Archive archive;
   181         protected final Set<Archive> requires;
   200         protected final Set<Archive> requires;
   182         protected final Set<Dep> deps;
   201         protected final Set<Dep> deps;
   183         protected final Type level;
   202         protected final Type level;
   184         private Profile profile;
   203         private Profile profile;
   185         ArchiveDeps(Archive archive, Type level) {
   204         Dependences(Archive archive, Type level) {
   186             this.archive = archive;
   205             this.archive = archive;
   187             this.deps = new HashSet<>();
   206             this.deps = new HashSet<>();
   188             this.requires = new HashSet<>();
   207             this.requires = new HashSet<>();
   189             this.level = level;
   208             this.level = level;
   190         }
   209         }
   196         Set<Archive> requires() {
   215         Set<Archive> requires() {
   197             return requires;
   216             return requires;
   198         }
   217         }
   199 
   218 
   200         Profile getTargetProfile(Archive target) {
   219         Profile getTargetProfile(Archive target) {
   201             if (target instanceof Module) {
   220             if (target.getModule().isJDK()) {
   202                 return Profile.getProfile((Module) target);
   221                 return Profile.getProfile((Module) target);
   203             } else {
   222             } else {
   204                 return null;
   223                 return null;
   205             }
   224             }
   206         }
   225         }
   207 
   226 
   208         Archive findArchive(Location t) {
   227         Archive findArchive(Location t) {
   209             Archive target = archive.getClasses().contains(t) ? archive : map.get(t);
   228             if (archive.getClasses().contains(t))
   210             if (target == null) {
   229                 return archive;
   211                 map.put(t, target = NOT_FOUND);
   230 
   212             }
   231             return locationToArchive.computeIfAbsent(t, _k -> NOT_FOUND);
   213             return target;
       
   214         }
   232         }
   215 
   233 
   216         // return classname or package name depedning on the level
   234         // return classname or package name depedning on the level
   217         private String getLocationName(Location o) {
   235         private String getLocationName(Location o) {
   218             if (level == Type.CLASS || level == Type.VERBOSE) {
   236             if (level == Type.CLASS || level == Type.VERBOSE) {
   230                 addDep(o, t);
   248                 addDep(o, t);
   231                 if (archive != targetArchive && !requires.contains(targetArchive)) {
   249                 if (archive != targetArchive && !requires.contains(targetArchive)) {
   232                     requires.add(targetArchive);
   250                     requires.add(targetArchive);
   233                 }
   251                 }
   234             }
   252             }
   235             if (targetArchive instanceof Module) {
   253             if (targetArchive.getModule().isNamed()) {
   236                 Profile p = Profile.getProfile(t.getPackageName());
   254                 Profile p = Profile.getProfile(t.getPackageName());
   237                 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
   255                 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
   238                     profile = p;
   256                     profile = p;
   239                 }
   257                 }
   240             }
   258             }
   313             return false;
   331             return false;
   314         }
   332         }
   315 
   333 
   316         @Override
   334         @Override
   317         public int hashCode() {
   335         public int hashCode() {
   318             int hash = 7;
   336             return Objects.hash(this.origin,
   319             hash = 67*hash + Objects.hashCode(this.origin)
   337                                 this.originArchive,
   320                            + Objects.hashCode(this.originArchive)
   338                                 this.target,
   321                            + Objects.hashCode(this.target)
   339                                 this.targetArchive);
   322                            + Objects.hashCode(this.targetArchive);
       
   323             return hash;
       
   324         }
   340         }
   325 
   341 
   326         public String toString() {
   342         public String toString() {
   327             return String.format("%s (%s) -> %s (%s)%n",
   343             return String.format("%s (%s) -> %s (%s)%n",
   328                     origin, originArchive.getName(),
   344                     origin, originArchive.getName(),
   329                     target, targetArchive.getName());
   345                     target, targetArchive.getName());
   330         }
   346         }
   331     }
   347     }
   332 
       
   333     static Analyzer getExportedAPIsAnalyzer() {
       
   334         return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.reexportsFilter, true);
       
   335     }
       
   336 
       
   337     static Analyzer getModuleAccessAnalyzer() {
       
   338         return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.accessCheckFilter, false);
       
   339     }
       
   340 
       
   341     private static class ModuleAccessAnalyzer extends Analyzer {
       
   342         private final boolean apionly;
       
   343         ModuleAccessAnalyzer(Filter filter, boolean apionly) {
       
   344             super(Type.VERBOSE, filter);
       
   345             this.apionly = apionly;
       
   346         }
       
   347         /**
       
   348          * Verify module access
       
   349          */
       
   350         public boolean run(List<Archive> archives) {
       
   351             // build a map from Location to Archive
       
   352             buildLocationArchiveMap(archives);
       
   353 
       
   354             // traverse and analyze all dependencies
       
   355             int count = 0;
       
   356             for (Archive archive : archives) {
       
   357                 ArchiveDeps checker = new ArchiveDeps(archive, type);
       
   358                 archive.visitDependences(checker);
       
   359                 count += checker.dependencies().size();
       
   360                 // output if any error
       
   361                 Module m = (Module)archive;
       
   362                 printDependences(System.err, m, checker.dependencies());
       
   363                 results.put(archive, checker);
       
   364             }
       
   365             return count == 0;
       
   366         }
       
   367 
       
   368         private void printDependences(PrintStream out, Module m, Set<Dep> deps) {
       
   369             if (deps.isEmpty())
       
   370                 return;
       
   371 
       
   372             String msg = apionly ? "API reference:" : "inaccessible reference:";
       
   373             deps.stream().sorted(Comparator.comparing(Dep::origin)
       
   374                                            .thenComparing(Dep::target))
       
   375                 .forEach(d -> out.format("%s %s (%s) -> %s (%s)%n", msg,
       
   376                                          d.origin(), d.originArchive().getName(),
       
   377                                          d.target(), d.targetArchive().getName()));
       
   378             if (apionly) {
       
   379                 out.format("Dependences missing re-exports=\"true\" attribute:%n");
       
   380                 deps.stream()
       
   381                         .map(Dep::targetArchive)
       
   382                         .map(Archive::getName)
       
   383                         .distinct()
       
   384                         .sorted()
       
   385                         .forEach(d -> out.format("  %s -> %s%n", m.name(), d));
       
   386             }
       
   387         }
       
   388 
       
   389         private static Module findModule(Archive archive) {
       
   390             if (Module.class.isInstance(archive)) {
       
   391                 return (Module) archive;
       
   392             } else {
       
   393                 return null;
       
   394             }
       
   395         }
       
   396 
       
   397         // returns true if target is accessible by origin
       
   398         private static boolean canAccess(Location o, Archive originArchive, Location t, Archive targetArchive) {
       
   399             Module origin = findModule(originArchive);
       
   400             Module target = findModule(targetArchive);
       
   401 
       
   402             if (targetArchive == Analyzer.NOT_FOUND) {
       
   403                 return false;
       
   404             }
       
   405 
       
   406             // unnamed module
       
   407             // ## should check public type?
       
   408             if (target == null)
       
   409                 return true;
       
   410 
       
   411             // module-private
       
   412             if (origin == target)
       
   413                 return true;
       
   414 
       
   415             return target.isAccessibleTo(t.getClassName(), origin);
       
   416         }
       
   417 
       
   418         static final Filter accessCheckFilter = new Filter() {
       
   419             @Override
       
   420             public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) {
       
   421                 return !canAccess(o, originArchive, t, targetArchive);
       
   422             }
       
   423         };
       
   424 
       
   425         static final Filter reexportsFilter = new Filter() {
       
   426             @Override
       
   427             public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) {
       
   428                 Module origin = findModule(originArchive);
       
   429                 Module target = findModule(targetArchive);
       
   430                 if (!origin.isExportedPackage(o.getPackageName())) {
       
   431                     // filter non-exported classes
       
   432                     return false;
       
   433                 }
       
   434 
       
   435                 boolean accessible = canAccess(o, originArchive, t, targetArchive);
       
   436                 if (!accessible)
       
   437                     return true;
       
   438 
       
   439                 String mn = target.name();
       
   440                 // skip checking re-exports for java.base
       
   441                 if (origin == target || "java.base".equals(mn))
       
   442                     return false;
       
   443 
       
   444                 assert origin.requires().containsKey(mn);  // otherwise, should not be accessible
       
   445                 if (origin.requires().get(mn)) {
       
   446                     return false;
       
   447                 }
       
   448                 return true;
       
   449             }
       
   450         };
       
   451     }
       
   452 }
   348 }