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.nio.file.Paths; 42 import java.util.Collections; 43 import java.util.Deque; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.Map; 47 import java.util.Optional; 48 import java.util.Set; 49 import java.util.concurrent.Callable; 50 import java.util.concurrent.ConcurrentHashMap; 51 import java.util.concurrent.ConcurrentLinkedDeque; 52 import java.util.concurrent.ExecutionException; 53 import java.util.concurrent.ExecutorService; 54 import java.util.concurrent.Executors; 55 import java.util.concurrent.FutureTask; 56 import java.util.stream.Stream; 57 58 /** 59 * Parses class files and finds dependences 60 */ 61 class DependencyFinder { 62 private static Finder API_FINDER = new Finder(true); 63 private static Finder CLASS_FINDER = new Finder(false); 64 65 private final JdepsConfiguration configuration; 66 private final JdepsFilter filter; 67 68 private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>(); 69 private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>(); 70 71 private final ExecutorService pool = Executors.newFixedThreadPool(2); 72 private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>(); 73 74 DependencyFinder(JdepsConfiguration configuration, 75 JdepsFilter filter) { 76 this.configuration = configuration; 77 this.filter = filter; 78 this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>()); 79 this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>()); 80 } 81 82 Map<Location, Archive> locationToArchive() { 83 return parsedClasses; 84 } 85 86 /** 87 * Returns the modules of all dependencies found 88 */ 89 Stream<Archive> getDependences(Archive source) { 90 return source.getDependencies() 91 .map(this::locationToArchive) 92 .filter(a -> a != source); 93 } 94 95 /** 96 * Returns the location to archive map; or NOT_FOUND. 97 * 98 * Location represents a parsed class. 99 */ 100 Archive locationToArchive(Location location) { 101 return parsedClasses.containsKey(location) 102 ? parsedClasses.get(location) 103 : configuration.findClass(location).orElse(NOT_FOUND); 104 } 105 106 /** 107 * Returns a map from an archive to its required archives 108 */ 109 Map<Archive, Set<Archive>> dependences() { 110 Map<Archive, Set<Archive>> map = new HashMap<>(); 111 parsedArchives.values().stream() 112 .flatMap(Deque::stream) 113 .filter(a -> !a.isEmpty()) 114 .forEach(source -> { 115 Set<Archive> deps = getDependences(source).collect(toSet()); 116 if (!deps.isEmpty()) { 117 map.put(source, deps); 118 } 119 }); 120 return map; 121 } 122 123 boolean isParsed(Location location) { 124 return parsedClasses.containsKey(location); 125 } 126 127 /** 128 * Parses all class files from the given archive stream and returns 129 * all target locations. 130 */ 131 public Set<Location> parse(Stream<? extends Archive> archiveStream) { 132 archiveStream.forEach(archive -> parse(archive, CLASS_FINDER)); 133 return waitForTasksCompleted(); 134 } 135 136 /** 137 * Parses the exported API class files from the given archive stream and 138 * returns all target locations. 139 */ 140 public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) { 141 archiveStream.forEach(archive -> parse(archive, API_FINDER)); 142 return waitForTasksCompleted(); 143 } 144 145 /** 146 * Parses the named class from the given archive and 147 * returns all target locations the named class references. 148 */ 149 public Set<Location> parse(Archive archive, String name) { 150 try { 151 return parse(archive, CLASS_FINDER, name); 152 } catch (IOException e) { 153 throw new UncheckedIOException(e); 154 } 155 } 156 157 /** 158 * Parses the exported API of the named class from the given archive and 159 * returns all target locations the named class references. 160 */ 161 public Set<Location> parseExportedAPIs(Archive archive, String name) 162 { 163 try { 164 return parse(archive, API_FINDER, name); 165 } catch (IOException e) { 166 throw new UncheckedIOException(e); 167 } 168 } 169 170 private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) { 171 if (parsedArchives.get(finder).contains(archive)) 172 return Optional.empty(); 173 174 parsedArchives.get(finder).add(archive); 175 176 trace("parsing %s %s%n", archive.getName(), archive.getPathName()); 177 FutureTask<Set<Location>> task = new FutureTask<>(() -> { 178 Set<Location> targets = new HashSet<>(); 179 for (ClassFile cf : archive.reader().getClassFiles()) { 180 if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 181 continue; 182 183 String classFileName; 184 try { 185 classFileName = cf.getName(); 186 } catch (ConstantPoolException e) { 187 throw new ClassFileError(e); 188 } 189 190 // filter source class/archive 191 String cn = classFileName.replace('/', '.'); 192 if (!finder.accept(archive, cn, cf.access_flags)) 193 continue; 194 195 // tests if this class matches the -include 196 if (!filter.matches(cn)) 197 continue; 198 199 for (Dependency d : finder.findDependencies(cf)) { 200 if (filter.accepts(d)) { 201 archive.addClass(d.getOrigin(), d.getTarget()); 202 targets.add(d.getTarget()); 203 } else { 204 // ensure that the parsed class is added the archive 205 archive.addClass(d.getOrigin()); 206 } 207 parsedClasses.putIfAbsent(d.getOrigin(), archive); 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 targets.addAll(task.get()); 268 } 269 return targets; 270 } catch (InterruptedException|ExecutionException e) { 271 throw new Error(e); 272 } 273 } 274 275 /* 276 * Shutdown the executor service. 277 */ 278 void shutdown() { 279 pool.shutdown(); 280 } 281 282 private interface SourceFilter { 283 boolean accept(Archive archive, String cn, AccessFlags accessFlags); 284 } 285 286 private static class Finder implements Dependency.Finder, SourceFilter { 287 private final Dependency.Finder finder; 288 private final boolean apiOnly; 289 Finder(boolean apiOnly) { 290 this.apiOnly = apiOnly; 291 this.finder = apiOnly 292 ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) 293 : Dependencies.getClassDependencyFinder(); 294 295 } 296 297 @Override 298 public boolean accept(Archive archive, String cn, AccessFlags accessFlags) { 299 int i = cn.lastIndexOf('.'); 300 String pn = i > 0 ? cn.substring(0, i) : ""; 301 302 // if -apionly is specified, analyze only exported and public types 303 // All packages are exported in unnamed module. 304 return apiOnly ? archive.getModule().isExported(pn) && 305 accessFlags.is(AccessFlags.ACC_PUBLIC) 306 : true; 307 } 308 309 @Override 310 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 311 return finder.findDependencies(classfile); 312 } 313 } 314 }