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