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