1 /*
   2  * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.tools.jdeps;
  26 
  27 import java.util.HashMap;
  28 import java.util.HashSet;
  29 import java.util.List;
  30 import java.util.Map;
  31 import java.util.Objects;
  32 import java.util.Set;
  33 import java.util.SortedMap;
  34 import java.util.SortedSet;
  35 import java.util.TreeMap;
  36 import java.util.TreeSet;
  37 
  38 import com.sun.tools.classfile.Dependency.Location;
  39 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
  40 
  41 /**
  42  * Dependency Analyzer.
  43  */
  44 public class Analyzer {
  45     /**
  46      * Type of the dependency analysis.  Appropriate level of data
  47      * will be stored.
  48      */
  49     public enum Type {
  50         SUMMARY,
  51         PACKAGE,
  52         CLASS,
  53         VERBOSE
  54     };
  55 
  56     /**
  57      * Filter to be applied when analyzing the dependencies from the given archives.
  58      * Only the accepted dependencies are recorded.
  59      */
  60     interface Filter {
  61         boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
  62     }
  63 
  64     private final Type type;
  65     private final Filter filter;
  66     private final Map<Archive, ArchiveDeps> results = new HashMap<>();
  67     private final Map<Location, Archive> map = new HashMap<>();
  68     private final Archive NOT_FOUND
  69         = new Archive(JdepsTask.getMessage("artifact.not.found"));
  70 
  71     /**
  72      * Constructs an Analyzer instance.
  73      *
  74      * @param type Type of the dependency analysis
  75      * @param filter
  76      */
  77     public Analyzer(Type type, Filter filter) {
  78         this.type = type;
  79         this.filter = filter;
  80     }
  81 
  82     /**
  83      * Performs the dependency analysis on the given archives.
  84      */
  85     public void run(List<Archive> archives) {
  86         // build a map from Location to Archive
  87         buildLocationArchiveMap(archives);
  88 
  89         // traverse and analyze all dependencies
  90         for (Archive archive : archives) {
  91             ArchiveDeps deps = new ArchiveDeps(archive, type);
  92             archive.visitDependences(deps);
  93             results.put(archive, deps);
  94         }
  95     }
  96 
  97     private void buildLocationArchiveMap(List<Archive> archives) {
  98         // build a map from Location to Archive
  99         for (Archive archive: archives) {
 100             for (Location l: archive.getClasses()) {
 101                 if (!map.containsKey(l)) {
 102                     map.put(l, archive);
 103                 } else {
 104                     // duplicated class warning?
 105                 }
 106             }
 107         }
 108     }
 109 
 110     public boolean hasDependences(Archive archive) {
 111         if (results.containsKey(archive)) {
 112             return results.get(archive).dependencies().size() > 0;
 113         }
 114         return false;
 115     }
 116 
 117     public Set<String> dependences(Archive source) {
 118         ArchiveDeps result = results.get(source);
 119         return result.targetDependences();
 120     }
 121 
 122     public interface Visitor {
 123         /**
 124          * Visits a recorded dependency from origin to target which can be
 125          * a fully-qualified classname, a package name, a module or
 126          * archive name depending on the Analyzer's type.
 127          */
 128         public void visitDependence(String origin, Archive originArchive,
 129                                     String target, Archive targetArchive);
 130     }
 131 
 132     /**
 133      * Visit the dependencies of the given source.
 134      * If the requested level is SUMMARY, it will visit the required archives list.
 135      */
 136     public void visitDependences(Archive source, Visitor v, Type level) {
 137         if (level == Type.SUMMARY) {
 138             final ArchiveDeps result = results.get(source);
 139             SortedMap<String, Archive> sorted = new TreeMap<>();
 140             for (Archive a : result.requires()) {
 141                 sorted.put(a.getName(), a);
 142             }
 143             for (Archive archive : sorted.values()) {
 144                 Profile profile = result.getTargetProfile(archive);
 145                 v.visitDependence(source.getName(), source,
 146                                   profile != null ? profile.profileName() : archive.getName(), archive);
 147             }
 148         } else {
 149             ArchiveDeps result = results.get(source);
 150             if (level != type) {
 151                 // requesting different level of analysis
 152                 result = new ArchiveDeps(source, level);
 153                 source.visitDependences(result);
 154             }
 155             SortedSet<Dep> sorted = new TreeSet<>(result.dependencies());
 156             for (Dep d : sorted) {
 157                 v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive());
 158             }
 159         }
 160     }
 161 
 162     public void visitDependences(Archive source, Visitor v) {
 163         visitDependences(source, v, type);
 164     }
 165 
 166     /**
 167      * ArchiveDeps contains the dependencies for an Archive that can have one or
 168      * more classes.
 169      */
 170     class ArchiveDeps implements Archive.Visitor {
 171         protected final Archive archive;
 172         protected final Set<Archive> requires;
 173         protected final Set<Dep> deps;
 174         protected final Type level;
 175         private Profile profile;
 176         ArchiveDeps(Archive archive, Type level) {
 177             this.archive = archive;
 178             this.deps = new HashSet<>();
 179             this.requires = new HashSet<>();
 180             this.level = level;
 181         }
 182 
 183         Set<Dep> dependencies() {
 184             return deps;
 185         }
 186 
 187         Set<String> targetDependences() {
 188             Set<String> targets = new HashSet<>();
 189             for (Dep d : deps) {
 190                 targets.add(d.target());
 191             }
 192             return targets;
 193         }
 194 
 195         Set<Archive> requires() {
 196             return requires;
 197         }
 198 
 199         Profile getTargetProfile(Archive target) {
 200             return JDKArchive.isProfileArchive(target) ? profile : null;
 201         }
 202 
 203         Archive findArchive(Location t) {
 204             Archive target = archive.getClasses().contains(t) ? archive : map.get(t);
 205             if (target == null) {
 206                 map.put(t, target = NOT_FOUND);
 207             }
 208             return target;
 209         }
 210 
 211         // return classname or package name depedning on the level
 212         private String getLocationName(Location o) {
 213             if (level == Type.CLASS || level == Type.VERBOSE) {
 214                 return o.getClassName();
 215             } else {
 216                 String pkg = o.getPackageName();
 217                 return pkg.isEmpty() ? "<unnamed>" : pkg;
 218             }
 219         }
 220 
 221         @Override
 222         public void visit(Location o, Location t) {
 223             Archive targetArchive = findArchive(t);
 224             if (filter.accepts(o, archive, t, targetArchive)) {
 225                 addDep(o, t);
 226                 if (!requires.contains(targetArchive)) {
 227                     requires.add(targetArchive);
 228                 }
 229             }
 230             if (targetArchive instanceof JDKArchive) {
 231                 Profile p = Profile.getProfile(t.getPackageName());
 232                 if (profile == null || (p != null && p.compareTo(profile) > 0)) {
 233                     profile = p;
 234                 }
 235             }
 236         }
 237 
 238         private Dep curDep;
 239         protected Dep addDep(Location o, Location t) {
 240             String origin = getLocationName(o);
 241             String target = getLocationName(t);
 242             Archive targetArchive = findArchive(t);
 243             if (curDep != null &&
 244                     curDep.origin().equals(origin) &&
 245                     curDep.originArchive() == archive &&
 246                     curDep.target().equals(target) &&
 247                     curDep.targetArchive() == targetArchive) {
 248                 return curDep;
 249             }
 250 
 251             Dep e = new Dep(origin, archive, target, targetArchive);
 252             if (deps.contains(e)) {
 253                 for (Dep e1 : deps) {
 254                     if (e.equals(e1)) {
 255                         curDep = e1;
 256                     }
 257                 }
 258             } else {
 259                 deps.add(e);
 260                 curDep = e;
 261             }
 262             return curDep;
 263         }
 264     }
 265 
 266     /*
 267      * Class-level or package-level dependency
 268      */
 269     class Dep implements Comparable<Dep> {
 270         final String origin;
 271         final Archive originArchive;
 272         final String target;
 273         final Archive targetArchive;
 274 
 275         Dep(String origin, Archive originArchive, String target, Archive targetArchive) {
 276             this.origin = origin;
 277             this.originArchive = originArchive;
 278             this.target = target;
 279             this.targetArchive = targetArchive;
 280         }
 281 
 282         String origin() {
 283             return origin;
 284         }
 285 
 286         Archive originArchive() {
 287             return originArchive;
 288         }
 289 
 290         String target() {
 291             return target;
 292         }
 293 
 294         Archive targetArchive() {
 295             return targetArchive;
 296         }
 297 
 298         @Override
 299         @SuppressWarnings("unchecked")
 300         public boolean equals(Object o) {
 301             if (o instanceof Dep) {
 302                 Dep d = (Dep) o;
 303                 return this.origin.equals(d.origin) &&
 304                         this.originArchive == d.originArchive &&
 305                         this.target.equals(d.target) &&
 306                         this.targetArchive == d.targetArchive;
 307             }
 308             return false;
 309         }
 310 
 311         @Override
 312         public int hashCode() {
 313             int hash = 7;
 314             hash = 67*hash + Objects.hashCode(this.origin)
 315                            + Objects.hashCode(this.originArchive)
 316                            + Objects.hashCode(this.target)
 317                            + Objects.hashCode(this.targetArchive);
 318             return hash;
 319         }
 320 
 321         @Override
 322         public int compareTo(Dep o) {
 323             if (this.origin.equals(o.origin)) {
 324                 if (this.target.equals(o.target)) {
 325                     if (this.originArchive == o.originArchive &&
 326                             this.targetArchive == o.targetArchive) {
 327                         return 0;
 328                     } else if (this.originArchive == o.originArchive) {
 329                         return this.targetArchive.getPathName().compareTo(o.targetArchive.getPathName());
 330                     } else {
 331                         return this.originArchive.getPathName().compareTo(o.originArchive.getPathName());
 332                     }
 333                 } else {
 334                     return this.target.compareTo(o.target);
 335                 }
 336             }
 337             return this.origin.compareTo(o.origin);
 338         }
 339     }
 340 }