langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DependencyFinder.java
author mcimadamore
Fri, 16 Dec 2016 15:27:34 +0000
changeset 42827 36468b5fa7f4
parent 42407 f3702cff2933
permissions -rw-r--r--
8181370: Convert anonymous inner classes into lambdas/method references Reviewed-by: jjg, rfield, mchung

/*
 * 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);
        }
    }
}