1 /*
2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package com.sun.tools.jdeps;
26 
27 import static com.sun.tools.jdeps.Module.*;
28 import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
29 import static java.util.stream.Collectors.*;
30 
31 import com.sun.tools.classfile.AccessFlags;
32 import com.sun.tools.classfile.ClassFile;
33 import com.sun.tools.classfile.ConstantPoolException;
34 import com.sun.tools.classfile.Dependencies;
35 import com.sun.tools.classfile.Dependencies.ClassFileError;
36 import com.sun.tools.classfile.Dependency;
37 import com.sun.tools.classfile.Dependency.Location;
38 
39 import java.io.IOException;
40 import java.io.UncheckedIOException;
41 import java.nio.file.Paths;
42 import java.util.Collections;
43 import java.util.Deque;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.Map;
47 import java.util.Optional;
48 import java.util.Set;
49 import java.util.concurrent.Callable;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.concurrent.ConcurrentLinkedDeque;
52 import java.util.concurrent.ExecutionException;
53 import java.util.concurrent.ExecutorService;
54 import java.util.concurrent.Executors;
55 import java.util.concurrent.FutureTask;
56 import java.util.stream.Stream;
57 
58 /**
59  * Parses class files and finds dependences
60  */
61 class DependencyFinder {
62     private static Finder API_FINDER = new Finder(true);
63     private static Finder CLASS_FINDER = new Finder(false);
64 
65     private final JdepsConfiguration configuration;
66     private final JdepsFilter filter;
67 
68     private final Map<Finder, Deque<Archive>> parsedArchives = new ConcurrentHashMap<>();
69     private final Map<Location, Archive> parsedClasses = new ConcurrentHashMap<>();
70 
71     private final ExecutorService pool = Executors.newFixedThreadPool(2);
72     private final Deque<FutureTask<Set<Location>>> tasks = new ConcurrentLinkedDeque<>();
73 
DependencyFinder(JdepsConfiguration configuration, JdepsFilter filter)74     DependencyFinder(JdepsConfiguration configuration,
75                      JdepsFilter filter) {
76         this.configuration = configuration;
77         this.filter = filter;
78         this.parsedArchives.put(API_FINDER, new ConcurrentLinkedDeque<>());
79         this.parsedArchives.put(CLASS_FINDER, new ConcurrentLinkedDeque<>());
80     }
81 
locationToArchive()82     Map<Location, Archive> locationToArchive() {
83         return parsedClasses;
84     }
85 
86     /**
87      * Returns the modules of all dependencies found
88      */
getDependences(Archive source)89     Stream<Archive> getDependences(Archive source) {
90         return source.getDependencies()
91                      .map(this::locationToArchive)
92                      .filter(a -> a != source);
93     }
94 
95     /**
96      * Returns the location to archive map; or NOT_FOUND.
97      *
98      * Location represents a parsed class.
99      */
locationToArchive(Location location)100     Archive locationToArchive(Location location) {
101         return parsedClasses.containsKey(location)
102             ? parsedClasses.get(location)
103             : configuration.findClass(location).orElse(NOT_FOUND);
104     }
105 
106     /**
107      * Returns a map from an archive to its required archives
108      */
dependences()109     Map<Archive, Set<Archive>> dependences() {
110         Map<Archive, Set<Archive>> map = new HashMap<>();
111         parsedArchives.values().stream()
112             .flatMap(Deque::stream)
113             .filter(a -> !a.isEmpty())
114             .forEach(source -> {
115                 Set<Archive> deps = getDependences(source).collect(toSet());
116                 if (!deps.isEmpty()) {
117                     map.put(source, deps);
118                 }
119         });
120         return map;
121     }
122 
isParsed(Location location)123     boolean isParsed(Location location) {
124         return parsedClasses.containsKey(location);
125     }
126 
127     /**
128      * Parses all class files from the given archive stream and returns
129      * all target locations.
130      */
parse(Stream<? extends Archive> archiveStream)131     public Set<Location> parse(Stream<? extends Archive> archiveStream) {
132         archiveStream.forEach(archive -> parse(archive, CLASS_FINDER));
133         return waitForTasksCompleted();
134     }
135 
136     /**
137      * Parses the exported API class files from the given archive stream and
138      * returns all target locations.
139      */
parseExportedAPIs(Stream<? extends Archive> archiveStream)140     public Set<Location> parseExportedAPIs(Stream<? extends Archive> archiveStream) {
141         archiveStream.forEach(archive -> parse(archive, API_FINDER));
142         return waitForTasksCompleted();
143     }
144 
145     /**
146      * Parses the named class from the given archive and
147      * returns all target locations the named class references.
148      */
parse(Archive archive, String name)149     public Set<Location> parse(Archive archive, String name) {
150         try {
151             return parse(archive, CLASS_FINDER, name);
152         } catch (IOException e) {
153             throw new UncheckedIOException(e);
154         }
155     }
156 
157     /**
158      * Parses the exported API of the named class from the given archive and
159      * returns all target locations the named class references.
160      */
parseExportedAPIs(Archive archive, String name)161     public Set<Location> parseExportedAPIs(Archive archive, String name)
162     {
163         try {
164             return parse(archive, API_FINDER, name);
165         } catch (IOException e) {
166             throw new UncheckedIOException(e);
167         }
168     }
169 
parse(Archive archive, Finder finder)170     private Optional<FutureTask<Set<Location>>> parse(Archive archive, Finder finder) {
171         if (parsedArchives.get(finder).contains(archive))
172             return Optional.empty();
173 
174         parsedArchives.get(finder).add(archive);
175 
176         trace("parsing %s %s%n", archive.getName(), archive.getPathName());
177         FutureTask<Set<Location>> task = new FutureTask<>(() -> {
178             Set<Location> targets = new HashSet<>();
179             for (ClassFile cf : archive.reader().getClassFiles()) {
180                 if (cf.access_flags.is(AccessFlags.ACC_MODULE))
181                     continue;
182 
183                 String classFileName;
184                 try {
185                     classFileName = cf.getName();
186                 } catch (ConstantPoolException e) {
187                     throw new ClassFileError(e);
188                 }
189 
190                 // filter source class/archive
191                 String cn = classFileName.replace('/', '.');
192                 if (!finder.accept(archive, cn, cf.access_flags))
193                     continue;
194 
195                 // tests if this class matches the -include
196                 if (!filter.matches(cn))
197                     continue;
198 
199                 for (Dependency d : finder.findDependencies(cf)) {
200                     if (filter.accepts(d)) {
201                         archive.addClass(d.getOrigin(), d.getTarget());
202                         targets.add(d.getTarget());
203                     } else {
204                         // ensure that the parsed class is added the archive
205                         archive.addClass(d.getOrigin());
206                     }
207                     parsedClasses.putIfAbsent(d.getOrigin(), archive);
208                 }
209             }
210             return targets;
211         });
212         tasks.add(task);
213         pool.submit(task);
214         return Optional.of(task);
215     }
216 
parse(Archive archive, Finder finder, String name)217     private Set<Location> parse(Archive archive, Finder finder, String name)
218         throws IOException
219     {
220         ClassFile cf = archive.reader().getClassFile(name);
221         if (cf == null) {
222             throw new IllegalArgumentException(archive.getName() +
223                 " does not contain " + name);
224         }
225 
226         if (cf.access_flags.is(AccessFlags.ACC_MODULE))
227             return Collections.emptySet();
228 
229         Set<Location> targets = new HashSet<>();
230         String cn;
231         try {
232             cn =  cf.getName().replace('/', '.');
233         } catch (ConstantPoolException e) {
234             throw new Dependencies.ClassFileError(e);
235         }
236 
237         if (!finder.accept(archive, cn, cf.access_flags))
238             return targets;
239 
240         // tests if this class matches the -include
241         if (!filter.matches(cn))
242             return targets;
243 
244         // skip checking filter.matches
245         for (Dependency d : finder.findDependencies(cf)) {
246             if (filter.accepts(d)) {
247                 targets.add(d.getTarget());
248                 archive.addClass(d.getOrigin(), d.getTarget());
249             } else {
250                 // ensure that the parsed class is added the archive
251                 archive.addClass(d.getOrigin());
252             }
253             parsedClasses.putIfAbsent(d.getOrigin(), archive);
254         }
255         return targets;
256     }
257 
258     /*
259      * Waits until all submitted tasks are completed.
260      */
waitForTasksCompleted()261     private Set<Location> waitForTasksCompleted() {
262         try {
263             Set<Location> targets = new HashSet<>();
264             FutureTask<Set<Location>> task;
265             while ((task = tasks.poll()) != null) {
266                 // wait for completion
267                 targets.addAll(task.get());
268             }
269             return targets;
270         } catch (InterruptedException|ExecutionException e) {
271             throw new Error(e);
272         }
273     }
274 
275     /*
276      * Shutdown the executor service.
277      */
shutdown()278     void shutdown() {
279         pool.shutdown();
280     }
281 
282     private interface SourceFilter {
accept(Archive archive, String cn, AccessFlags accessFlags)283         boolean accept(Archive archive, String cn, AccessFlags accessFlags);
284     }
285 
286     private static class Finder implements Dependency.Finder, SourceFilter {
287         private final Dependency.Finder finder;
288         private final boolean apiOnly;
Finder(boolean apiOnly)289         Finder(boolean apiOnly) {
290             this.apiOnly = apiOnly;
291             this.finder = apiOnly
292                 ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
293                 : Dependencies.getClassDependencyFinder();
294 
295         }
296 
297         @Override
accept(Archive archive, String cn, AccessFlags accessFlags)298         public boolean accept(Archive archive, String cn, AccessFlags accessFlags) {
299             int i = cn.lastIndexOf('.');
300             String pn = i > 0 ? cn.substring(0, i) : "";
301 
302             // if -apionly is specified, analyze only exported and public types
303             // All packages are exported in unnamed module.
304             return apiOnly ? archive.getModule().isExported(pn) &&
305                                  accessFlags.is(AccessFlags.ACC_PUBLIC)
306                            : true;
307         }
308 
309         @Override
findDependencies(ClassFile classfile)310         public Iterable<? extends Dependency> findDependencies(ClassFile classfile) {
311             return finder.findDependencies(classfile);
312         }
313     }
314 }
315