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 26 package com.sun.tools.jdeps; 27 28 import com.sun.tools.classfile.Dependency.Location; 29 30 import java.io.BufferedReader; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.InputStreamReader; 34 import java.io.UncheckedIOException; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Map; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 45 /** 46 * Dependency Analyzer. 47 */ 48 public 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 MODULE, // equivalent to summary in addition, print module descriptor 56 PACKAGE, 57 CLASS, 58 VERBOSE 59 } 60 61 /** 62 * Filter to be applied when analyzing the dependencies from the given archives. 63 * Only the accepted dependencies are recorded. 64 */ 65 interface Filter { 66 boolean accepts(Location origin, Archive originArchive, 67 Location target, Archive targetArchive); 68 } 69 70 protected final JdepsConfiguration configuration; 71 protected final Type type; 72 protected final Filter filter; 73 protected final Map<Archive, Dependences> results = new HashMap<>(); 74 protected final Map<Location, Archive> locationToArchive = new HashMap<>(); 75 static final Archive NOT_FOUND 76 = new Archive(JdepsTask.getMessage("artifact.not.found")); 77 78 /** 79 * Constructs an Analyzer instance. 80 * 81 * @param type Type of the dependency analysis 82 * @param filter 83 */ 84 Analyzer(JdepsConfiguration config, Type type, Filter filter) { 85 this.configuration = config; 86 this.type = type; 87 this.filter = filter; 88 } 89 90 /** 91 * Performs the dependency analysis on the given archives. 92 */ 93 boolean run(Iterable<? extends Archive> archives, 94 Map<Location, Archive> locationMap) 95 { 96 this.locationToArchive.putAll(locationMap); 97 98 // traverse and analyze all dependencies 99 for (Archive archive : archives) { 100 Dependences deps = new Dependences(archive, type); 101 archive.visitDependences(deps); 102 results.put(archive, deps); 103 } 104 return true; 105 } 106 107 /** 108 * Returns the analyzed archives 109 */ 110 Set<Archive> archives() { 111 return results.keySet(); 112 } 113 114 /** 115 * Returns true if the given archive has dependences. 116 */ 117 boolean hasDependences(Archive archive) { 118 if (results.containsKey(archive)) { 119 return results.get(archive).dependencies().size() > 0; 120 } 121 return false; 122 } 123 124 /** 125 * Returns the dependences, either class name or package name 126 * as specified in the given verbose level, from the given source. 127 */ 128 Set<String> dependences(Archive source) { 129 if (!results.containsKey(source)) { 130 return Collections.emptySet(); 131 } 132 133 return results.get(source).dependencies() 134 .stream() 135 .map(Dep::target) 136 .collect(Collectors.toSet()); 137 } 138 139 /** 140 * Returns the direct dependences of the given source 141 */ 142 Stream<Archive> requires(Archive source) { 143 if (!results.containsKey(source)) { 144 return Stream.empty(); 145 } 146 return results.get(source).requires() 147 .stream(); 148 } 149 150 interface Visitor { 151 /** 152 * Visits a recorded dependency from origin to target which can be 153 * a fully-qualified classname, a package name, a module or 154 * archive name depending on the Analyzer's type. 155 */ 156 public void visitDependence(String origin, Archive originArchive, 157 String target, Archive targetArchive); 158 } 159 160 /** 161 * Visit the dependencies of the given source. 162 * If the requested level is SUMMARY, it will visit the required archives list. 163 */ 164 void visitDependences(Archive source, Visitor v, Type level) { 165 if (level == Type.SUMMARY) { 166 final Dependences result = results.get(source); 167 final Set<Archive> reqs = result.requires(); 168 Stream<Archive> stream = reqs.stream(); 169 if (reqs.isEmpty()) { 170 if (hasDependences(source)) { 171 // If reqs.isEmpty() and we have dependences, then it means 172 // that the dependences are from 'source' onto itself. 173 stream = Stream.of(source); 174 } 175 } 176 stream.sorted(Comparator.comparing(Archive::getName)) 177 .forEach(archive -> { 178 Profile profile = result.getTargetProfile(archive); 179 v.visitDependence(source.getName(), source, 180 profile != null ? profile.profileName() 181 : archive.getName(), archive); 182 }); 183 } else { 184 Dependences result = results.get(source); 185 if (level != type) { 186 // requesting different level of analysis 187 result = new Dependences(source, level); 188 source.visitDependences(result); 189 } 190 result.dependencies().stream() 191 .sorted(Comparator.comparing(Dep::origin) 192 .thenComparing(Dep::target)) 193 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), 194 d.target(), d.targetArchive())); 195 } 196 } 197 198 void visitDependences(Archive source, Visitor v) { 199 visitDependences(source, v, type); 200 } 201 202 /** 203 * Dependences contains the dependencies for an Archive that can have one or 204 * more classes. 205 */ 206 class Dependences implements Archive.Visitor { 207 protected final Archive archive; 208 protected final Set<Archive> requires; 209 protected final Set<Dep> deps; 210 protected final Type level; 211 private Profile profile; 212 Dependences(Archive archive, Type level) { 213 this.archive = archive; 214 this.deps = new HashSet<>(); 215 this.requires = new HashSet<>(); 216 this.level = level; 217 } 218 219 Set<Dep> dependencies() { 220 return deps; 221 } 222 223 Set<Archive> requires() { 224 return requires; 225 } 226 227 Profile getTargetProfile(Archive target) { 228 if (target.getModule().isJDK()) { 229 return Profile.getProfile((Module) target); 230 } else { 231 return null; 232 } 233 } 234 235 /* 236 * Returns the archive that contains the given location. 237 */ 238 Archive findArchive(Location t) { 239 // local in this archive 240 if (archive.getClasses().contains(t)) 241 return archive; 242 243 Archive target; 244 if (locationToArchive.containsKey(t)) { 245 target = locationToArchive.get(t); 246 } else { 247 // special case JDK removed API 248 target = configuration.findClass(t) 249 .orElseGet(() -> REMOVED_JDK_INTERNALS.contains(t) 250 ? REMOVED_JDK_INTERNALS 251 : NOT_FOUND); 252 } 253 return locationToArchive.computeIfAbsent(t, _k -> target); 254 } 255 256 // return classname or package name depending on the level 257 private String getLocationName(Location o) { 258 if (level == Type.CLASS || level == Type.VERBOSE) { 259 return o.getClassName(); 260 } else { 261 String pkg = o.getPackageName(); 262 return pkg.isEmpty() ? "<unnamed>" : pkg; 263 } 264 } 265 266 @Override 267 public void visit(Location o, Location t) { 268 Archive targetArchive = findArchive(t); 269 if (filter.accepts(o, archive, t, targetArchive)) { 270 addDep(o, t); 271 if (archive != targetArchive && !requires.contains(targetArchive)) { 272 requires.add(targetArchive); 273 } 274 } 275 if (targetArchive.getModule().isNamed()) { 276 Profile p = Profile.getProfile(t.getPackageName()); 277 if (profile == null || (p != null && p.compareTo(profile) > 0)) { 278 profile = p; 279 } 280 } 281 } 282 283 private Dep curDep; 284 protected Dep addDep(Location o, Location t) { 285 String origin = getLocationName(o); 286 String target = getLocationName(t); 287 Archive targetArchive = findArchive(t); 288 if (curDep != null && 289 curDep.origin().equals(origin) && 290 curDep.originArchive() == archive && 291 curDep.target().equals(target) && 292 curDep.targetArchive() == targetArchive) { 293 return curDep; 294 } 295 296 Dep e = new Dep(origin, archive, target, targetArchive); 297 if (deps.contains(e)) { 298 for (Dep e1 : deps) { 299 if (e.equals(e1)) { 300 curDep = e1; 301 } 302 } 303 } else { 304 deps.add(e); 305 curDep = e; 306 } 307 return curDep; 308 } 309 } 310 311 /* 312 * Class-level or package-level dependency 313 */ 314 class Dep { 315 final String origin; 316 final Archive originArchive; 317 final String target; 318 final Archive targetArchive; 319 320 Dep(String origin, Archive originArchive, String target, Archive targetArchive) { 321 this.origin = origin; 322 this.originArchive = originArchive; 323 this.target = target; 324 this.targetArchive = targetArchive; 325 } 326 327 String origin() { 328 return origin; 329 } 330 331 Archive originArchive() { 332 return originArchive; 333 } 334 335 String target() { 336 return target; 337 } 338 339 Archive targetArchive() { 340 return targetArchive; 341 } 342 343 @Override 344 @SuppressWarnings("unchecked") 345 public boolean equals(Object o) { 346 if (o instanceof Dep) { 347 Dep d = (Dep) o; 348 return this.origin.equals(d.origin) && 349 this.originArchive == d.originArchive && 350 this.target.equals(d.target) && 351 this.targetArchive == d.targetArchive; 352 } 353 return false; 354 } 355 356 @Override 357 public int hashCode() { 358 return Objects.hash(this.origin, 359 this.originArchive, 360 this.target, 361 this.targetArchive); 362 } 363 364 public String toString() { 365 return String.format("%s (%s) -> %s (%s)%n", 366 origin, originArchive.getName(), 367 target, targetArchive.getName()); 368 } 369 } 370 371 static final Jdk8Internals REMOVED_JDK_INTERNALS = new Jdk8Internals(); 372 373 static class Jdk8Internals extends Module { 374 private final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt"; 375 private final Set<String> jdk8Internals; 376 private Jdk8Internals() { 377 super("JDK removed internal API"); 378 try (InputStream in = JdepsTask.class.getResourceAsStream(JDK8_INTERNALS); 379 BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { 380 this.jdk8Internals = reader.lines() 381 .filter(ln -> !ln.startsWith("#")) 382 .collect(Collectors.toSet()); 383 } catch (IOException e) { 384 throw new UncheckedIOException(e); 385 } 386 } 387 388 public boolean contains(Location location) { 389 String cn = location.getClassName(); 390 int i = cn.lastIndexOf('.'); 391 String pn = i > 0 ? cn.substring(0, i) : ""; 392 393 if (!jdk8Internals.contains(pn)) { 394 return false; 395 } 396 397 return jdk8Internals.contains(pn); 398 } 399 400 @Override 401 public String name() { 402 return getName(); 403 } 404 405 @Override 406 public boolean isJDK() { 407 return true; 408 } 409 410 @Override 411 public boolean isExported(String pn) { 412 return false; 413 } 414 } 415 }