| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.tools.jdeps; |
| |
| import static com.sun.tools.jdeps.Module.*; |
| import static com.sun.tools.jdeps.Analyzer.NOT_FOUND; |
| import static java.util.stream.Collectors.*; |
| |
| import com.sun.tools.classfile.AccessFlags; |
| import com.sun.tools.classfile.ClassFile; |
| import com.sun.tools.classfile.ConstantPoolException; |
| import com.sun.tools.classfile.Dependencies; |
| import com.sun.tools.classfile.Dependencies.ClassFileError; |
| import com.sun.tools.classfile.Dependency; |
| import com.sun.tools.classfile.Dependency.Location; |
| |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentLinkedDeque; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.FutureTask; |
| import java.util.stream.Stream; |
| |
| /** |
| * Parses class files and finds dependences |
| */ |
| class DependencyFinder { |
| private static Finder API_FINDER = new Finder(true); |
| private static Finder CLASS_FINDER = new Finder(false); |
| |
| private final JdepsConfiguration configuration; |
| private final JdepsFilter filter; |
| |
| private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>(); |
| private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>(); |
| |
| private final ExecutorService pool = Executors.newFixedThreadPool(2); |
| private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>(); |
| |
| DependencyFinder(JdepsConfiguration configuration, |
| JdepsFilter filter) { |
| this.configuration = configuration; |
| this.filter = filter; |
| this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>()); |
| this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>()); |
| } |
| |
| Map<Location, Archive> locationToArchive() { |
| return parsedClasses; |
| } |
| |
| /** |
| * Returns the modules of all dependencies found |
| */ |
| Stream<Archive> getDependences(Archive source) { |
| return source.getDependencies() |
| .map(this::locationToArchive) |
| .filter(a -> a != source); |
| } |
| |
| /** |
| * Returns the location to archive map; or NOT_FOUND. |
| * |
| * Location represents a parsed class. |
| */ |
| Archive locationToArchive(Location location) { |
| return parsedClasses.containsKey(location) |
| ? parsedClasses.get(location) |
| : configuration.findClass(location).orElse(NOT_FOUND); |
| } |
| |
| /** |
| * Returns a map from an archive to its required archives |
| */ |
| Map<Archive, Set<Archive>> dependences() { |
| Map<Archive, Set<Archive>> map = new HashMap<>(); |
| parsedArchives.values().stream() |
| .flatMap(Deque::stream) |
| .filter(a -> !a.isEmpty()) |
| .forEach(source -> { |
| Set<Archive> deps = getDependences(source).collect(toSet()); |
| if (!deps.isEmpty()) { |
| map.put(source, deps); |
| } |
| }); |
| return map; |
| } |
| |
| boolean isParsed(Location location) { |
| return parsedClasses.containsKey(location); |
| } |
| |
| /** |
| * Parses all class files from the given archive stream and returns |
| * all target locations. |
| */ |
| public Set<Location> parse(Stream<? extends Archive> archiveStream) { |
| archiveStream.forEach(archive -> parse(archive, CLASS_FINDER)); |
| return waitForTasksCompleted(); |
| } |
| |
| /** |
| * Parses the exported API class files from the given archive stream and |
| * returns all target locations. |
| */ |
| public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) { |
| archiveStream.forEach(archive -> parse(archive, API_FINDER)); |
| return waitForTasksCompleted(); |
| } |
| |
| /** |
| * Parses the named class from the given archive and |
| * returns all target locations the named class references. |
| */ |
| public Set<Location> parse(Archive archive, String name) { |
| try { |
| return parse(archive, CLASS_FINDER, name); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| /** |
| * Parses the exported API of the named class from the given archive and |
| * returns all target locations the named class references. |
| */ |
| public Set<Location> parseExportedAPIs(Archive archive, String name) |
| { |
| try { |
| return parse(archive, API_FINDER, name); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) { |
| if (parsedArchives.get(finder).contains(archive)) |
| return Optional.empty(); |
| |
| parsedArchives.get(finder).add(archive); |
| |
| trace("parsing %s %s%n", archive.getName(), archive.path()); |
| FutureTask<Set<Location>> task = new FutureTask<>(() -> { |
| Set<Location> targets = new HashSet<>(); |
| for (ClassFile cf : archive.reader().getClassFiles()) { |
| if (cf.access_flags.is(AccessFlags.ACC_MODULE)) |
| continue; |
| |
| String classFileName; |
| try { |
| classFileName = cf.getName(); |
| } catch (ConstantPoolException e) { |
| throw new ClassFileError(e); |
| } |
| |
| // filter source class/archive |
| String cn = classFileName.replace('/', '.'); |
| if (!finder.accept(archive, cn, cf.access_flags)) |
| continue; |
| |
| // tests if this class matches the -include |
| if (!filter.matches(cn)) |
| continue; |
| |
| for (Dependency d : finder.findDependencies(cf)) { |
| if (filter.accepts(d)) { |
| archive.addClass(d.getOrigin(), d.getTarget()); |
| targets.add(d.getTarget()); |
| } else { |
| // ensure that the parsed class is added the archive |
| archive.addClass(d.getOrigin()); |
| } |
| parsedClasses.putIfAbsent(d.getOrigin(), archive); |
| } |
| } |
| |
| return targets; |
| }); |
| tasks.add(task); |
| pool.submit(task); |
| return Optional.of(task); |
| } |
| |
| private Set<Location> parse(Archive archive, Finder finder, String name) |
| throws IOException |
| { |
| ClassFile cf = archive.reader().getClassFile(name); |
| if (cf == null) { |
| throw new IllegalArgumentException(archive.getName() + |
| " does not contain " + name); |
| } |
| |
| if (cf.access_flags.is(AccessFlags.ACC_MODULE)) |
| return Collections.emptySet(); |
| |
| Set<Location> targets = new HashSet<>(); |
| String cn; |
| try { |
| cn = cf.getName().replace('/', '.'); |
| } catch (ConstantPoolException e) { |
| throw new Dependencies.ClassFileError(e); |
| } |
| |
| if (!finder.accept(archive, cn, cf.access_flags)) |
| return targets; |
| |
| // tests if this class matches the -include |
| if (!filter.matches(cn)) |
| return targets; |
| |
| // skip checking filter.matches |
| for (Dependency d : finder.findDependencies(cf)) { |
| if (filter.accepts(d)) { |
| targets.add(d.getTarget()); |
| archive.addClass(d.getOrigin(), d.getTarget()); |
| } else { |
| // ensure that the parsed class is added the archive |
| archive.addClass(d.getOrigin()); |
| } |
| parsedClasses.putIfAbsent(d.getOrigin(), archive); |
| } |
| return targets; |
| } |
| |
| /* |
| * Waits until all submitted tasks are completed. |
| */ |
| private Set<Location> waitForTasksCompleted() { |
| try { |
| Set<Location> targets = new HashSet<>(); |
| FutureTask<Set<Location>> task; |
| while ((task = tasks.poll()) != null) { |
| // wait for completion |
| if (!task.isDone()) |
| targets.addAll(task.get()); |
| } |
| return targets; |
| } catch (InterruptedException|ExecutionException e) { |
| throw new Error(e); |
| } |
| } |
| |
| /* |
| * Shutdown the executor service. |
| */ |
| void shutdown() { |
| pool.shutdown(); |
| } |
| |
| private interface SourceFilter { |
| boolean accept(Archive archive, String cn, AccessFlags accessFlags); |
| } |
| |
| private static class Finder implements Dependency.Finder, SourceFilter { |
| private final Dependency.Finder finder; |
| private final boolean apiOnly; |
| Finder(boolean apiOnly) { |
| this.apiOnly = apiOnly; |
| this.finder = apiOnly |
| ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) |
| : Dependencies.getClassDependencyFinder(); |
| |
| } |
| |
| @Override |
| public boolean accept(Archive archive, String cn, AccessFlags accessFlags) { |
| int i = cn.lastIndexOf('.'); |
| String pn = i > 0 ? cn.substring(0, i) : ""; |
| |
| // if -apionly is specified, analyze only exported and public types |
| // All packages are exported in unnamed module. |
| return apiOnly ? archive.getModule().isExported(pn) && |
| accessFlags.is(AccessFlags.ACC_PUBLIC) |
| : true; |
| } |
| |
| @Override |
| public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { |
| return finder.findDependencies(classfile); |
| } |
| } |
| } |