Analyzer.java revision 3294:9adfb22ff08f
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 26package com.sun.tools.jdeps; 27 28import java.io.PrintStream; 29import java.util.ArrayList; 30import java.util.Collections; 31import java.util.Comparator; 32import java.util.Deque; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.LinkedList; 36import java.util.List; 37import java.util.Map; 38import java.util.Objects; 39import java.util.Set; 40import java.util.stream.Collectors; 41import java.util.stream.Stream; 42 43import com.sun.tools.classfile.Dependency.Location; 44 45/** 46 * Dependency Analyzer. 47 */ 48public class Analyzer { 49 /** 50 * Type of the dependency analysis. Appropriate level of data 51 * will be stored. 52 */ 53 public enum Type { 54 SUMMARY, 55 PACKAGE, 56 CLASS, 57 VERBOSE 58 } 59 60 /** 61 * Filter to be applied when analyzing the dependencies from the given archives. 62 * Only the accepted dependencies are recorded. 63 */ 64 interface Filter { 65 boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive); 66 } 67 68 protected final Type type; 69 protected final Filter filter; 70 protected final Map<Archive, Dependences> results = new HashMap<>(); 71 protected final Map<Location, Archive> locationToArchive = new HashMap<>(); 72 static final Archive NOT_FOUND 73 = new Archive(JdepsTask.getMessage("artifact.not.found")); 74 75 /** 76 * Constructs an Analyzer instance. 77 * 78 * @param type Type of the dependency analysis 79 * @param filter 80 */ 81 public Analyzer(Type type, Filter filter) { 82 this.type = type; 83 this.filter = filter; 84 } 85 86 /** 87 * Performs the dependency analysis on the given archives. 88 */ 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) { 97 // build a map from Location to Archive 98 buildLocationArchiveMap(archives); 99 100 // traverse and analyze all dependencies 101 for (Archive archive : archives) { 102 Dependences deps = new Dependences(archive, type); 103 archive.visitDependences(deps); 104 results.put(archive, deps); 105 } 106 return true; 107 } 108 109 protected void buildLocationArchiveMap(Iterable<? extends Archive> archives) { 110 // build a map from Location to Archive 111 for (Archive archive: archives) { 112 archive.getClasses() 113 .forEach(l -> locationToArchive.putIfAbsent(l, archive)); 114 } 115 } 116 117 public boolean hasDependences(Archive archive) { 118 if (results.containsKey(archive)) { 119 return results.get(archive).dependencies().size() > 0; 120 } 121 return false; 122 } 123 124 public Set<String> dependences(Archive source) { 125 if (!results.containsKey(source)) { 126 return Collections.emptySet(); 127 } 128 Dependences result = results.get(source); 129 return result.dependencies().stream() 130 .map(Dep::target) 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()); 140 } 141 142 public interface Visitor { 143 /** 144 * Visits a recorded dependency from origin to target which can be 145 * a fully-qualified classname, a package name, a module or 146 * archive name depending on the Analyzer's type. 147 */ 148 public void visitDependence(String origin, Archive originArchive, 149 String target, Archive targetArchive); 150 } 151 152 /** 153 * Visit the dependencies of the given source. 154 * If the requested level is SUMMARY, it will visit the required archives list. 155 */ 156 public void visitDependences(Archive source, Visitor v, Type level) { 157 if (level == Type.SUMMARY) { 158 final Dependences result = results.get(source); 159 final Set<Archive> reqs = result.requires(); 160 Stream<Archive> stream = reqs.stream(); 161 if (reqs.isEmpty()) { 162 if (hasDependences(source)) { 163 // If reqs.isEmpty() and we have dependences, then it means 164 // that the dependences are from 'source' onto itself. 165 stream = Stream.of(source); 166 } 167 } 168 stream.sorted(Comparator.comparing(Archive::getName)) 169 .forEach(archive -> { 170 Profile profile = result.getTargetProfile(archive); 171 v.visitDependence(source.getName(), source, 172 profile != null ? profile.profileName() 173 : archive.getName(), archive); 174 }); 175 } else { 176 Dependences result = results.get(source); 177 if (level != type) { 178 // requesting different level of analysis 179 result = new Dependences(source, level); 180 source.visitDependences(result); 181 } 182 result.dependencies().stream() 183 .sorted(Comparator.comparing(Dep::origin) 184 .thenComparing(Dep::target)) 185 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), 186 d.target(), d.targetArchive())); 187 } 188 } 189 190 public void visitDependences(Archive source, Visitor v) { 191 visitDependences(source, v, type); 192 } 193 194 /** 195 * Dependences contains the dependencies for an Archive that can have one or 196 * more classes. 197 */ 198 class Dependences implements Archive.Visitor { 199 protected final Archive archive; 200 protected final Set<Archive> requires; 201 protected final Set<Dep> deps; 202 protected final Type level; 203 private Profile profile; 204 Dependences(Archive archive, Type level) { 205 this.archive = archive; 206 this.deps = new HashSet<>(); 207 this.requires = new HashSet<>(); 208 this.level = level; 209 } 210 211 Set<Dep> dependencies() { 212 return deps; 213 } 214 215 Set<Archive> requires() { 216 return requires; 217 } 218 219 Profile getTargetProfile(Archive target) { 220 if (target.getModule().isJDK()) { 221 return Profile.getProfile((Module) target); 222 } else { 223 return null; 224 } 225 } 226 227 Archive findArchive(Location t) { 228 if (archive.getClasses().contains(t)) 229 return archive; 230 231 return locationToArchive.computeIfAbsent(t, _k -> NOT_FOUND); 232 } 233 234 // return classname or package name depedning on the level 235 private String getLocationName(Location o) { 236 if (level == Type.CLASS || level == Type.VERBOSE) { 237 return o.getClassName(); 238 } else { 239 String pkg = o.getPackageName(); 240 return pkg.isEmpty() ? "<unnamed>" : pkg; 241 } 242 } 243 244 @Override 245 public void visit(Location o, Location t) { 246 Archive targetArchive = findArchive(t); 247 if (filter.accepts(o, archive, t, targetArchive)) { 248 addDep(o, t); 249 if (archive != targetArchive && !requires.contains(targetArchive)) { 250 requires.add(targetArchive); 251 } 252 } 253 if (targetArchive.getModule().isNamed()) { 254 Profile p = Profile.getProfile(t.getPackageName()); 255 if (profile == null || (p != null && p.compareTo(profile) > 0)) { 256 profile = p; 257 } 258 } 259 } 260 261 private Dep curDep; 262 protected Dep addDep(Location o, Location t) { 263 String origin = getLocationName(o); 264 String target = getLocationName(t); 265 Archive targetArchive = findArchive(t); 266 if (curDep != null && 267 curDep.origin().equals(origin) && 268 curDep.originArchive() == archive && 269 curDep.target().equals(target) && 270 curDep.targetArchive() == targetArchive) { 271 return curDep; 272 } 273 274 Dep e = new Dep(origin, archive, target, targetArchive); 275 if (deps.contains(e)) { 276 for (Dep e1 : deps) { 277 if (e.equals(e1)) { 278 curDep = e1; 279 } 280 } 281 } else { 282 deps.add(e); 283 curDep = e; 284 } 285 return curDep; 286 } 287 } 288 289 /* 290 * Class-level or package-level dependency 291 */ 292 class Dep { 293 final String origin; 294 final Archive originArchive; 295 final String target; 296 final Archive targetArchive; 297 298 Dep(String origin, Archive originArchive, String target, Archive targetArchive) { 299 this.origin = origin; 300 this.originArchive = originArchive; 301 this.target = target; 302 this.targetArchive = targetArchive; 303 } 304 305 String origin() { 306 return origin; 307 } 308 309 Archive originArchive() { 310 return originArchive; 311 } 312 313 String target() { 314 return target; 315 } 316 317 Archive targetArchive() { 318 return targetArchive; 319 } 320 321 @Override 322 @SuppressWarnings("unchecked") 323 public boolean equals(Object o) { 324 if (o instanceof Dep) { 325 Dep d = (Dep) o; 326 return this.origin.equals(d.origin) && 327 this.originArchive == d.originArchive && 328 this.target.equals(d.target) && 329 this.targetArchive == d.targetArchive; 330 } 331 return false; 332 } 333 334 @Override 335 public int hashCode() { 336 return Objects.hash(this.origin, 337 this.originArchive, 338 this.target, 339 this.targetArchive); 340 } 341 342 public String toString() { 343 return String.format("%s (%s) -> %s (%s)%n", 344 origin, originArchive.getName(), 345 target, targetArchive.getName()); 346 } 347 } 348} 349