DependencyFinder.java revision 3827:44bdefe64114
176479Swpaul/* 276479Swpaul * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 376479Swpaul * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 476479Swpaul * 576479Swpaul * This code is free software; you can redistribute it and/or modify it 676479Swpaul * under the terms of the GNU General Public License version 2 only, as 776479Swpaul * published by the Free Software Foundation. Oracle designates this 876479Swpaul * particular file as subject to the "Classpath" exception as provided 976479Swpaul * by Oracle in the LICENSE file that accompanied this code. 1076479Swpaul * 1176479Swpaul * This code is distributed in the hope that it will be useful, but WITHOUT 1276479Swpaul * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1376479Swpaul * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1476479Swpaul * version 2 for more details (a copy is included in the LICENSE file that 1576479Swpaul * accompanied this code). 1676479Swpaul * 1776479Swpaul * You should have received a copy of the GNU General Public License version 1876479Swpaul * 2 along with this work; if not, write to the Free Software Foundation, 1976479Swpaul * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2076479Swpaul * 2176479Swpaul * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2276479Swpaul * or visit www.oracle.com if you need additional information or have any 2376479Swpaul * questions. 2476479Swpaul */ 2576479Swpaulpackage com.sun.tools.jdeps; 2676479Swpaul 2776479Swpaulimport static com.sun.tools.jdeps.Module.*; 2876479Swpaulimport static com.sun.tools.jdeps.Analyzer.NOT_FOUND; 2976479Swpaulimport static java.util.stream.Collectors.*; 3076479Swpaul 3176479Swpaulimport com.sun.tools.classfile.AccessFlags; 3276479Swpaulimport com.sun.tools.classfile.ClassFile; 3376479Swpaulimport com.sun.tools.classfile.ConstantPoolException; 34119418Sobrienimport com.sun.tools.classfile.Dependencies; 35119418Sobrienimport com.sun.tools.classfile.Dependencies.ClassFileError; 36119418Sobrienimport com.sun.tools.classfile.Dependency; 3776479Swpaulimport com.sun.tools.classfile.Dependency.Location; 3876479Swpaul 3976479Swpaulimport java.io.IOException; 4076479Swpaulimport java.io.UncheckedIOException; 4176479Swpaulimport java.util.Collections; 4276479Swpaulimport java.util.Deque; 4376479Swpaulimport java.util.HashMap; 4476479Swpaulimport java.util.HashSet; 4576479Swpaulimport java.util.Map; 4676479Swpaulimport java.util.Optional; 4776479Swpaulimport java.util.Set; 4876479Swpaulimport java.util.concurrent.Callable; 4976479Swpaulimport java.util.concurrent.ConcurrentHashMap; 5076479Swpaulimport java.util.concurrent.ConcurrentLinkedDeque; 5176479Swpaulimport java.util.concurrent.ExecutionException; 5276479Swpaulimport java.util.concurrent.ExecutorService; 5376479Swpaulimport java.util.concurrent.Executors; 5476479Swpaulimport java.util.concurrent.FutureTask; 5576479Swpaulimport java.util.stream.Stream; 5676479Swpaul 5776479Swpaul/** 5876479Swpaul * Parses class files and finds dependences 5976479Swpaul */ 6076479Swpaulclass DependencyFinder { 6176479Swpaul private static Finder API_FINDER = new Finder(true); 6276479Swpaul private static Finder CLASS_FINDER = new Finder(false); 6376479Swpaul 6476479Swpaul private final JdepsConfiguration configuration; 6576479Swpaul private final JdepsFilter filter; 6676479Swpaul 6776479Swpaul private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>(); 6876479Swpaul private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>(); 6976479Swpaul 7076479Swpaul private final ExecutorService pool = Executors.newFixedThreadPool(2); 7176479Swpaul private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>(); 7276479Swpaul 7378323Swpaul DependencyFinder(JdepsConfiguration configuration, 7478323Swpaul JdepsFilter filter) { 7578323Swpaul this.configuration = configuration; 7678323Swpaul this.filter = filter; 7778323Swpaul this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>()); 7878323Swpaul this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>()); 7978323Swpaul } 8078323Swpaul 8178323Swpaul Map<Location, Archive> locationToArchive() { 8278323Swpaul return parsedClasses; 8378323Swpaul } 8478323Swpaul 8578323Swpaul /** 8678323Swpaul * Returns the modules of all dependencies found 8778323Swpaul */ 8878323Swpaul Stream<Archive> getDependences(Archive source) { 8976479Swpaul return source.getDependencies() 9076479Swpaul .map(this::locationToArchive) 9176479Swpaul .filter(a -> a != source); 9276479Swpaul } 9376479Swpaul 9476479Swpaul /** 9576479Swpaul * Returns the location to archive map; or NOT_FOUND. 96129879Sphk * 9776479Swpaul * Location represents a parsed class. 9876479Swpaul */ 9976479Swpaul Archive locationToArchive(Location location) { 10076479Swpaul return parsedClasses.containsKey(location) 10176479Swpaul ? parsedClasses.get(location) 10276479Swpaul : configuration.findClass(location).orElse(NOT_FOUND); 10376479Swpaul } 10476479Swpaul 10576479Swpaul /** 10676479Swpaul * Returns a map from an archive to its required archives 10776479Swpaul */ 10876479Swpaul Map<Archive, Set<Archive>> dependences() { 10976479Swpaul Map<Archive, Set<Archive>> map = new HashMap<>(); 11076479Swpaul parsedArchives.values().stream() 11176479Swpaul .flatMap(Deque::stream) 11276479Swpaul .filter(a -> !a.isEmpty()) 11376479Swpaul .forEach(source -> { 11476479Swpaul Set<Archive> deps = getDependences(source).collect(toSet()); 11576479Swpaul if (!deps.isEmpty()) { 11676479Swpaul map.put(source, deps); 11776479Swpaul } 11876479Swpaul }); 11976479Swpaul return map; 12076479Swpaul } 12176479Swpaul 12276479Swpaul boolean isParsed(Location location) { 123119285Simp return parsedClasses.containsKey(location); 124119285Simp } 12576479Swpaul 12676479Swpaul /** 12776479Swpaul * Parses all class files from the given archive stream and returns 12876522Swpaul * all target locations. 12976479Swpaul */ 130113506Smdodd public Set<Location> parse(Stream<? extends Archive> archiveStream) { 131113506Smdodd archiveStream.forEach(archive -> parse(archive, CLASS_FINDER)); 13276479Swpaul return waitForTasksCompleted(); 13376479Swpaul } 13476479Swpaul 13576479Swpaul /** 13676479Swpaul * Parses the exported API class files from the given archive stream and 13776479Swpaul * returns all target locations. 13876479Swpaul */ 13976479Swpaul public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) { 14076479Swpaul archiveStream.forEach(archive -> parse(archive, API_FINDER)); 14176479Swpaul return waitForTasksCompleted(); 14276479Swpaul } 14376479Swpaul 14476479Swpaul /** 14576479Swpaul * Parses the named class from the given archive and 14676479Swpaul * returns all target locations the named class references. 14776479Swpaul */ 14899497Salfred public Set<Location> parse(Archive archive, String name) { 14999497Salfred try { 15099497Salfred return parse(archive, CLASS_FINDER, name); 15176479Swpaul } catch (IOException e) { 15299497Salfred throw new UncheckedIOException(e); 15399497Salfred } 15499497Salfred } 15599497Salfred 15676479Swpaul /** 15799497Salfred * Parses the exported API of the named class from the given archive and 15899497Salfred * returns all target locations the named class references. 15999497Salfred */ 16099497Salfred public Set<Location> parseExportedAPIs(Archive archive, String name) 16199497Salfred { 16299497Salfred try { 16399497Salfred return parse(archive, API_FINDER, name); 16499497Salfred } catch (IOException e) { 16599497Salfred throw new UncheckedIOException(e); 16699497Salfred } 16799497Salfred } 16899497Salfred 16999497Salfred private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) { 17099497Salfred if (parsedArchives.get(finder).contains(archive)) 17176479Swpaul return Optional.empty(); 17299497Salfred 17399497Salfred parsedArchives.get(finder).add(archive); 17499497Salfred 17599497Salfred trace("parsing %s %s%n", archive.getName(), archive.path()); 17699497Salfred FutureTask<Set<Location>> task = new FutureTask<>(() -> { 17776479Swpaul Set<Location> targets = new HashSet<>(); 17899497Salfred for (ClassFile cf : archive.reader().getClassFiles()) { 17999497Salfred if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 18099497Salfred continue; 18199497Salfred 18276479Swpaul String classFileName; 18399497Salfred try { 18499497Salfred classFileName = cf.getName(); 18599497Salfred } catch (ConstantPoolException e) { 18676479Swpaul throw new ClassFileError(e); 18799497Salfred } 18899497Salfred 18999497Salfred // filter source class/archive 19099497Salfred String cn = classFileName.replace('/', '.'); 19176479Swpaul if (!finder.accept(archive, cn, cf.access_flags)) 19276479Swpaul continue; 19376479Swpaul 19476479Swpaul // tests if this class matches the -include 19576479Swpaul if (!filter.matches(cn)) 19676479Swpaul continue; 19776479Swpaul 19876479Swpaul for (Dependency d : finder.findDependencies(cf)) { 19976479Swpaul if (filter.accepts(d)) { 20076479Swpaul archive.addClass(d.getOrigin(), d.getTarget()); 20176479Swpaul targets.add(d.getTarget()); 20276479Swpaul } else { 20376479Swpaul // ensure that the parsed class is added the archive 20476479Swpaul archive.addClass(d.getOrigin()); 20576479Swpaul } 20676479Swpaul parsedClasses.putIfAbsent(d.getOrigin(), archive); 20776479Swpaul } 20876479Swpaul } 20976479Swpaul 21076479Swpaul return targets; 21176479Swpaul }); 21276479Swpaul tasks.add(task); 21376479Swpaul pool.submit(task); 21476479Swpaul return Optional.of(task); 21576479Swpaul } 21676479Swpaul 21776479Swpaul private Set<Location> parse(Archive archive, Finder finder, String name) 21876479Swpaul throws IOException 21976479Swpaul { 22076479Swpaul ClassFile cf = archive.reader().getClassFile(name); 22176479Swpaul if (cf == null) { 22276479Swpaul throw new IllegalArgumentException(archive.getName() + 22376479Swpaul " does not contain " + name); 22476479Swpaul } 22576479Swpaul 22676479Swpaul if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 227113506Smdodd return Collections.emptySet(); 22876479Swpaul 22976479Swpaul Set<Location> targets = new HashSet<>(); 23076479Swpaul String cn; 23176479Swpaul try { 23276479Swpaul cn = cf.getName().replace('/', '.'); 23376479Swpaul } catch (ConstantPoolException e) { 23476479Swpaul throw new Dependencies.ClassFileError(e); 23576479Swpaul } 23676479Swpaul 23776479Swpaul if (!finder.accept(archive, cn, cf.access_flags)) 23876479Swpaul return targets; 239106696Salfred 24076479Swpaul // tests if this class matches the -include 24176479Swpaul if (!filter.matches(cn)) 242106696Salfred return targets; 24376479Swpaul 24499497Salfred // skip checking filter.matches 24599497Salfred for (Dependency d : finder.findDependencies(cf)) { 24676479Swpaul if (filter.accepts(d)) { 24776479Swpaul targets.add(d.getTarget()); 24876479Swpaul archive.addClass(d.getOrigin(), d.getTarget()); 24976479Swpaul } else { 25076479Swpaul // ensure that the parsed class is added the archive 25176479Swpaul archive.addClass(d.getOrigin()); 25276479Swpaul } 25376479Swpaul parsedClasses.putIfAbsent(d.getOrigin(), archive); 25476479Swpaul } 25576479Swpaul return targets; 25699497Salfred } 25799497Salfred 25876479Swpaul /* 25976479Swpaul * Waits until all submitted tasks are completed. 26076479Swpaul */ 26176479Swpaul private Set<Location> waitForTasksCompleted() { 26276479Swpaul try { 26376479Swpaul Set<Location> targets = new HashSet<>(); 26476479Swpaul FutureTask<Set<Location>> task; 26576479Swpaul while ((task = tasks.poll()) != null) { 26676479Swpaul // wait for completion 26776479Swpaul if (!task.isDone()) 26876479Swpaul targets.addAll(task.get()); 26976479Swpaul } 27076479Swpaul return targets; 27176479Swpaul } catch (InterruptedException|ExecutionException e) { 27276479Swpaul throw new Error(e); 27376479Swpaul } 27476479Swpaul } 27576479Swpaul 27676479Swpaul /* 27776479Swpaul * Shutdown the executor service. 27876479Swpaul */ 27976479Swpaul void shutdown() { 28076479Swpaul pool.shutdown(); 28176479Swpaul } 28276479Swpaul 28376479Swpaul private interface SourceFilter { 28476479Swpaul boolean accept(Archive archive, String cn, AccessFlags accessFlags); 28576479Swpaul } 28699497Salfred 28799497Salfred private static class Finder implements Dependency.Finder, SourceFilter { 28876479Swpaul private final Dependency.Finder finder; 28976479Swpaul private final boolean apiOnly; 29076479Swpaul Finder(boolean apiOnly) { 29176479Swpaul this.apiOnly = apiOnly; 29276479Swpaul this.finder = apiOnly 29376479Swpaul ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) 29476479Swpaul : Dependencies.getClassDependencyFinder(); 29576479Swpaul 29676479Swpaul } 29776479Swpaul 29876479Swpaul @Override 29976479Swpaul public boolean accept(Archive archive, String cn, AccessFlags accessFlags) { 30076479Swpaul int i = cn.lastIndexOf('.'); 30176479Swpaul String pn = i > 0 ? cn.substring(0, i) : ""; 30276479Swpaul 30376479Swpaul // if -apionly is specified, analyze only exported and public types 30476479Swpaul // All packages are exported in unnamed module. 30576479Swpaul return apiOnly ? archive.getModule().isExported(pn) && 30676479Swpaul accessFlags.is(AccessFlags.ACC_PUBLIC) 30776479Swpaul : true; 30876479Swpaul } 30976479Swpaul 31076479Swpaul @Override 31176479Swpaul public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 31276479Swpaul return finder.findDependencies(classfile); 31376479Swpaul } 31476479Swpaul } 31576479Swpaul} 31676479Swpaul