Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DependencyFinder.java
41161 views
/*1* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package com.sun.tools.jdeps;2526import static com.sun.tools.jdeps.Module.*;27import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;28import static java.util.stream.Collectors.*;2930import com.sun.tools.classfile.AccessFlags;31import com.sun.tools.classfile.ClassFile;32import com.sun.tools.classfile.ConstantPoolException;33import com.sun.tools.classfile.Dependencies;34import com.sun.tools.classfile.Dependencies.ClassFileError;35import com.sun.tools.classfile.Dependency;36import com.sun.tools.classfile.Dependency.Location;3738import java.io.IOException;39import java.io.UncheckedIOException;40import java.nio.file.Paths;41import java.util.Collections;42import java.util.Deque;43import java.util.HashMap;44import java.util.HashSet;45import java.util.Map;46import java.util.Optional;47import java.util.Set;48import java.util.concurrent.Callable;49import java.util.concurrent.ConcurrentHashMap;50import java.util.concurrent.ConcurrentLinkedDeque;51import java.util.concurrent.ExecutionException;52import java.util.concurrent.ExecutorService;53import java.util.concurrent.Executors;54import java.util.concurrent.FutureTask;55import java.util.stream.Stream;5657/**58* Parses class files and finds dependences59*/60class DependencyFinder {61private static Finder API_FINDER = new Finder(true);62private static Finder CLASS_FINDER = new Finder(false);6364private final JdepsConfiguration configuration;65private final JdepsFilter filter;6667private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>();68private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>();6970private final ExecutorService pool = Executors.newFixedThreadPool(2);71private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>();7273DependencyFinder(JdepsConfiguration configuration,74JdepsFilter filter) {75this.configuration = configuration;76this.filter = filter;77this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>());78this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>());79}8081Map<Location, Archive> locationToArchive() {82return parsedClasses;83}8485/**86* Returns the modules of all dependencies found87*/88Stream<Archive> getDependences(Archive source) {89return source.getDependencies()90.map(this::locationToArchive)91.filter(a -> a != source);92}9394/**95* Returns the location to archive map; or NOT_FOUND.96*97* Location represents a parsed class.98*/99Archive locationToArchive(Location location) {100return parsedClasses.containsKey(location)101? parsedClasses.get(location)102: configuration.findClass(location).orElse(NOT_FOUND);103}104105/**106* Returns a map from an archive to its required archives107*/108Map<Archive, Set<Archive>> dependences() {109Map<Archive, Set<Archive>> map = new HashMap<>();110parsedArchives.values().stream()111.flatMap(Deque::stream)112.filter(a -> !a.isEmpty())113.forEach(source -> {114Set<Archive> deps = getDependences(source).collect(toSet());115if (!deps.isEmpty()) {116map.put(source, deps);117}118});119return map;120}121122boolean isParsed(Location location) {123return parsedClasses.containsKey(location);124}125126/**127* Parses all class files from the given archive stream and returns128* all target locations.129*/130public Set<Location> parse(Stream<? extends Archive> archiveStream) {131archiveStream.forEach(archive -> parse(archive, CLASS_FINDER));132return waitForTasksCompleted();133}134135/**136* Parses the exported API class files from the given archive stream and137* returns all target locations.138*/139public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) {140archiveStream.forEach(archive -> parse(archive, API_FINDER));141return waitForTasksCompleted();142}143144/**145* Parses the named class from the given archive and146* returns all target locations the named class references.147*/148public Set<Location> parse(Archive archive, String name) {149try {150return parse(archive, CLASS_FINDER, name);151} catch (IOException e) {152throw new UncheckedIOException(e);153}154}155156/**157* Parses the exported API of the named class from the given archive and158* returns all target locations the named class references.159*/160public Set<Location> parseExportedAPIs(Archive archive, String name)161{162try {163return parse(archive, API_FINDER, name);164} catch (IOException e) {165throw new UncheckedIOException(e);166}167}168169private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) {170if (parsedArchives.get(finder).contains(archive))171return Optional.empty();172173parsedArchives.get(finder).add(archive);174175trace("parsing %s %s%n", archive.getName(), archive.getPathName());176FutureTask<Set<Location>> task = new FutureTask<>(() -> {177Set<Location> targets = new HashSet<>();178for (ClassFile cf : archive.reader().getClassFiles()) {179if (cf.access_flags.is(AccessFlags.ACC_MODULE))180continue;181182String classFileName;183try {184classFileName = cf.getName();185} catch (ConstantPoolException e) {186throw new ClassFileError(e);187}188189// filter source class/archive190String cn = classFileName.replace('/', '.');191if (!finder.accept(archive, cn, cf.access_flags))192continue;193194// tests if this class matches the -include195if (!filter.matches(cn))196continue;197198for (Dependency d : finder.findDependencies(cf)) {199if (filter.accepts(d)) {200archive.addClass(d.getOrigin(), d.getTarget());201targets.add(d.getTarget());202} else {203// ensure that the parsed class is added the archive204archive.addClass(d.getOrigin());205}206parsedClasses.putIfAbsent(d.getOrigin(), archive);207}208}209return targets;210});211tasks.add(task);212pool.submit(task);213return Optional.of(task);214}215216private Set<Location> parse(Archive archive, Finder finder, String name)217throws IOException218{219ClassFile cf = archive.reader().getClassFile(name);220if (cf == null) {221throw new IllegalArgumentException(archive.getName() +222" does not contain " + name);223}224225if (cf.access_flags.is(AccessFlags.ACC_MODULE))226return Collections.emptySet();227228Set<Location> targets = new HashSet<>();229String cn;230try {231cn = cf.getName().replace('/', '.');232} catch (ConstantPoolException e) {233throw new Dependencies.ClassFileError(e);234}235236if (!finder.accept(archive, cn, cf.access_flags))237return targets;238239// tests if this class matches the -include240if (!filter.matches(cn))241return targets;242243// skip checking filter.matches244for (Dependency d : finder.findDependencies(cf)) {245if (filter.accepts(d)) {246targets.add(d.getTarget());247archive.addClass(d.getOrigin(), d.getTarget());248} else {249// ensure that the parsed class is added the archive250archive.addClass(d.getOrigin());251}252parsedClasses.putIfAbsent(d.getOrigin(), archive);253}254return targets;255}256257/*258* Waits until all submitted tasks are completed.259*/260private Set<Location> waitForTasksCompleted() {261try {262Set<Location> targets = new HashSet<>();263FutureTask<Set<Location>> task;264while ((task = tasks.poll()) != null) {265// wait for completion266targets.addAll(task.get());267}268return targets;269} catch (InterruptedException|ExecutionException e) {270throw new Error(e);271}272}273274/*275* Shutdown the executor service.276*/277void shutdown() {278pool.shutdown();279}280281private interface SourceFilter {282boolean accept(Archive archive, String cn, AccessFlags accessFlags);283}284285private static class Finder implements Dependency.Finder, SourceFilter {286private final Dependency.Finder finder;287private final boolean apiOnly;288Finder(boolean apiOnly) {289this.apiOnly = apiOnly;290this.finder = apiOnly291? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)292: Dependencies.getClassDependencyFinder();293294}295296@Override297public boolean accept(Archive archive, String cn, AccessFlags accessFlags) {298int i = cn.lastIndexOf('.');299String pn = i > 0 ? cn.substring(0, i) : "";300301// if -apionly is specified, analyze only exported and public types302// All packages are exported in unnamed module.303return apiOnly ? archive.getModule().isExported(pn) &&304accessFlags.is(AccessFlags.ACC_PUBLIC)305: true;306}307308@Override309public Iterable<? extends Dependency> findDependencies(ClassFile classfile) {310return finder.findDependencies(classfile);311}312}313}314315316