1 /* 2 * Copyright (c) 2015, 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 static com.sun.tools.jdeps.Module.*; 28 import static com.sun.tools.jdeps.Analyzer.NOT_FOUND; 29 import static java.util.stream.Collectors.*; 30 31 import com.sun.tools.classfile.AccessFlags; 32 import com.sun.tools.classfile.ClassFile; 33 import com.sun.tools.classfile.ConstantPoolException; 34 import com.sun.tools.classfile.Dependencies; 35 import com.sun.tools.classfile.Dependencies.ClassFileError; 36 import com.sun.tools.classfile.Dependency; 37 import com.sun.tools.classfile.Dependency.Location; 38 39 import java.io.IOException; 40 import java.io.UncheckedIOException; 41 import java.util.Collections; 42 import java.util.Deque; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.Map; 46 import java.util.Optional; 47 import java.util.Set; 48 import java.util.concurrent.Callable; 49 import java.util.concurrent.ConcurrentHashMap; 50 import java.util.concurrent.ConcurrentLinkedDeque; 51 import java.util.concurrent.ExecutionException; 52 import java.util.concurrent.ExecutorService; 53 import java.util.concurrent.Executors; 54 import java.util.concurrent.FutureTask; 55 import java.util.stream.Stream; 56 57 /** 58 * Parses class files and finds dependences 59 */ 60 class DependencyFinder { 61 private static Finder API_FINDER = new Finder(true); 62 private static Finder CLASS_FINDER = new Finder(false); 63 64 private final JdepsConfiguration configuration; 65 private final JdepsFilter filter; 66 67 private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>(); 68 private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>(); 69 70 private final ExecutorService pool = Executors.newFixedThreadPool(2); 71 private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>(); 72 73 DependencyFinder(JdepsConfiguration configuration, 74 JdepsFilter filter) { 75 this.configuration = configuration; 76 this.filter = filter; 77 this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>()); 78 this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>()); 79 } 80 81 Map<Location, Archive> locationToArchive() { 82 return parsedClasses; 83 } 84 85 /** 86 * Returns the modules of all dependencies found 87 */ 88 Stream<Archive> getDependences(Archive source) { 89 return source.getDependencies() 90 .map(this::locationToArchive) 91 .filter(a -> a != source); 92 } 93 94 /** 95 * Returns the location to archive map; or NOT_FOUND. 96 * 97 * Location represents a parsed class. 98 */ 99 Archive locationToArchive(Location location) { 100 return parsedClasses.containsKey(location) 101 ? parsedClasses.get(location) 102 : configuration.findClass(location).orElse(NOT_FOUND); 103 } 104 105 /** 106 * Returns a map from an archive to its required archives 107 */ 108 Map<Archive, Set<Archive>> dependences() { 109 Map<Archive, Set<Archive>> map = new HashMap<>(); 110 parsedArchives.values().stream() 111 .flatMap(Deque::stream) 112 .filter(a -> !a.isEmpty()) 113 .forEach(source -> { 114 Set<Archive> deps = getDependences(source).collect(toSet()); 115 if (!deps.isEmpty()) { 116 map.put(source, deps); 117 } 118 }); 119 return map; 120 } 121 122 boolean isParsed(Location location) { 123 return parsedClasses.containsKey(location); 124 } 125 126 /** 127 * Parses all class files from the given archive stream and returns 128 * all target locations. 129 */ 130 public Set<Location> parse(Stream<? extends Archive> archiveStream) { 131 archiveStream.forEach(archive -> parse(archive, CLASS_FINDER)); 132 return waitForTasksCompleted(); 133 } 134 135 /** 136 * Parses the exported API class files from the given archive stream and 137 * returns all target locations. 138 */ 139 public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) { 140 archiveStream.forEach(archive -> parse(archive, API_FINDER)); 141 return waitForTasksCompleted(); 142 } 143 144 /** 145 * Parses the named class from the given archive and 146 * returns all target locations the named class references. 147 */ 148 public Set<Location> parse(Archive archive, String name) { 149 try { 150 return parse(archive, CLASS_FINDER, name); 151 } catch (IOException e) { 152 throw new UncheckedIOException(e); 153 } 154 } 155 156 /** 157 * Parses the exported API of the named class from the given archive and 158 * returns all target locations the named class references. 159 */ 160 public Set<Location> parseExportedAPIs(Archive archive, String name) 161 { 162 try { 163 return parse(archive, API_FINDER, name); 164 } catch (IOException e) { 165 throw new UncheckedIOException(e); 166 } 167 } 168 169 private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) { 170 if (parsedArchives.get(finder).contains(archive)) 171 return Optional.empty(); 172 173 parsedArchives.get(finder).add(archive); 174 175 trace("parsing %s %s%n", archive.getName(), archive.path()); 176 FutureTask<Set<Location>> task = new FutureTask<>(() -> { 177 Set<Location> targets = new HashSet<>(); 178 for (ClassFile cf : archive.reader().getClassFiles()) { 179 if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 180 continue; 181 182 String classFileName; 183 try { 184 classFileName = cf.getName(); 185 } catch (ConstantPoolException e) { 186 throw new ClassFileError(e); 187 } 188 189 // filter source class/archive 190 String cn = classFileName.replace('/', '.'); 191 if (!finder.accept(archive, cn, cf.access_flags)) 192 continue; 193 194 // tests if this class matches the -include 195 if (!filter.matches(cn)) 196 continue; 197 198 for (Dependency d : finder.findDependencies(cf)) { 199 if (filter.accepts(d)) { 200 archive.addClass(d.getOrigin(), d.getTarget()); 201 targets.add(d.getTarget()); 202 } else { 203 // ensure that the parsed class is added the archive 204 archive.addClass(d.getOrigin()); 205 } 206 parsedClasses.putIfAbsent(d.getOrigin(), archive); 207 } 208 } 209 210 return targets; 211 }); 212 tasks.add(task); 213 pool.submit(task); 214 return Optional.of(task); 215 } 216 217 private Set<Location> parse(Archive archive, Finder finder, String name) 218 throws IOException 219 { 220 ClassFile cf = archive.reader().getClassFile(name); 221 if (cf == null) { 222 throw new IllegalArgumentException(archive.getName() + 223 " does not contain " + name); 224 } 225 226 if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 227 return Collections.emptySet(); 228 229 Set<Location> targets = new HashSet<>(); 230 String cn; 231 try { 232 cn = cf.getName().replace('/', '.'); 233 } catch (ConstantPoolException e) { 234 throw new Dependencies.ClassFileError(e); 235 } 236 237 if (!finder.accept(archive, cn, cf.access_flags)) 238 return targets; 239 240 // tests if this class matches the -include 241 if (!filter.matches(cn)) 242 return targets; 243 244 // skip checking filter.matches 245 for (Dependency d : finder.findDependencies(cf)) { 246 if (filter.accepts(d)) { 247 targets.add(d.getTarget()); 248 archive.addClass(d.getOrigin(), d.getTarget()); 249 } else { 250 // ensure that the parsed class is added the archive 251 archive.addClass(d.getOrigin()); 252 } 253 parsedClasses.putIfAbsent(d.getOrigin(), archive); 254 } 255 return targets; 256 } 257 258 /* 259 * Waits until all submitted tasks are completed. 260 */ 261 private Set<Location> waitForTasksCompleted() { 262 try { 263 Set<Location> targets = new HashSet<>(); 264 FutureTask<Set<Location>> task; 265 while ((task = tasks.poll()) != null) { 266 // wait for completion 267 if (!task.isDone()) 268 targets.addAll(task.get()); 269 } 270 return targets; 271 } catch (InterruptedException|ExecutionException e) { 272 throw new Error(e); 273 } 274 } 275 276 /* 277 * Shutdown the executor service. 278 */ 279 void shutdown() { 280 pool.shutdown(); 281 } 282 283 private interface SourceFilter { 284 boolean accept(Archive archive, String cn, AccessFlags accessFlags); 285 } 286 287 private static class Finder implements Dependency.Finder, SourceFilter { 288 private final Dependency.Finder finder; 289 private final boolean apiOnly; 290 Finder(boolean apiOnly) { 291 this.apiOnly = apiOnly; 292 this.finder = apiOnly 293 ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) 294 : Dependencies.getClassDependencyFinder(); 295 296 } 297 298 @Override 299 public boolean accept(Archive archive, String cn, AccessFlags accessFlags) { 300 int i = cn.lastIndexOf('.'); 301 String pn = i > 0 ? cn.substring(0, i) : ""; 302 303 // if -apionly is specified, analyze only exported and public types 304 // All packages are exported in unnamed module. 305 return apiOnly ? archive.getModule().isExported(pn) && 306 accessFlags.is(AccessFlags.ACC_PUBLIC) 307 : true; 308 } 309 310 @Override 311 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 312 return finder.findDependencies(classfile); 313 } 314 } 315 }