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
26 package com.sun.tools.jdeps;
27
28 import com.sun.tools.classfile.Dependency.Location;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Deque;
32 import java.util.LinkedHashSet;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.concurrent.ConcurrentLinkedDeque;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40
41 import static com.sun.tools.jdeps.Analyzer.Type.CLASS;
42 import static com.sun.tools.jdeps.Analyzer.Type.VERBOSE;
43 import static com.sun.tools.jdeps.Module.trace;
44 import static java.util.stream.Collectors.*;
45
46 /**
47 * Dependency Analyzer.
48 *
49 * Type of filters:
50 * source filter: -include <pattern>
74 JdepsFilter filter,
75 JdepsWriter writer,
76 Analyzer.Type verbose,
77 boolean apiOnly) {
78 this.configuration = config;
79 this.filter = filter;
80 this.writer = writer;
81 this.verbose = verbose;
82 this.apiOnly = apiOnly;
83
84 this.finder = new DependencyFinder(config, filter);
85 this.analyzer = new Analyzer(configuration, verbose, filter);
86
87 // determine initial archives to be analyzed
88 this.rootArchives.addAll(configuration.initialArchives());
89
90 // if -include pattern is specified, add the matching archives on
91 // classpath to the root archives
92 if (filter.hasIncludePattern() || filter.hasTargetFilter()) {
93 configuration.getModules().values().stream()
94 .filter(source -> filter.include(source) && filter.matches(source))
95 .forEach(this.rootArchives::add);
96 }
97
98 // class path archives
99 configuration.classPathArchives().stream()
100 .filter(filter::matches)
101 .forEach(this.rootArchives::add);
102
103 // Include the root modules for analysis
104 this.rootArchives.addAll(configuration.rootModules());
105
106 trace("analyze root archives: %s%n", this.rootArchives);
107 }
108
109 /*
110 * Perform runtime dependency analysis
111 */
112 public boolean run() throws IOException {
113 return run(false, 1);
114 }
144 // analyze the dependencies collected
145 analyzer.run(archives, finder.locationToArchive());
146
147 if (writer != null) {
148 writer.generateOutput(archives, analyzer);
149 }
150 } finally {
151 finder.shutdown();
152 }
153 return true;
154 }
155
156 /**
157 * Returns the archives for reporting that has matching dependences.
158 *
159 * If --require is set, they should be excluded.
160 */
161 Set<Archive> archives() {
162 if (filter.requiresFilter().isEmpty()) {
163 return archives.stream()
164 .filter(filter::include)
165 .filter(Archive::hasDependences)
166 .collect(Collectors.toSet());
167 } else {
168 // use the archives that have dependences and not specified in --require
169 return archives.stream()
170 .filter(filter::include)
171 .filter(source -> !filter.requiresFilter().contains(source))
172 .filter(source ->
173 source.getDependencies()
174 .map(finder::locationToArchive)
175 .anyMatch(a -> a != source))
176 .collect(Collectors.toSet());
177 }
178 }
179
180 /**
181 * Returns the dependences, either class name or package name
182 * as specified in the given verbose level.
183 */
184 Set<String> dependences() {
185 return analyzer.archives().stream()
186 .map(analyzer::dependences)
187 .flatMap(Set::stream)
188 .collect(Collectors.toSet());
189 }
190
191 /**
192 * Returns the archives that contains the given locations and
193 * not parsed and analyzed.
194 */
195 private Set<Archive> unresolvedArchives(Stream<Location> locations) {
196 return locations.filter(l -> !finder.isParsed(l))
197 .distinct()
198 .map(configuration::findClass)
199 .flatMap(Optional::stream)
200 .filter(filter::include)
201 .collect(toSet());
202 }
203
204 /*
205 * Recursively analyzes entire module/archives.
206 */
207 private void transitiveArchiveDeps(int depth) throws IOException {
208 Stream<Location> deps = archives.stream()
209 .flatMap(Archive::getDependencies);
210
211 // start with the unresolved archives
212 Set<Archive> unresolved = unresolvedArchives(deps);
213 do {
214 // parse all unresolved archives
215 Set<Location> targets = apiOnly
216 ? finder.parseExportedAPIs(unresolved.stream())
217 : finder.parse(unresolved.stream());
218 archives.addAll(unresolved);
219
220 // Add dependencies to the next batch for analysis
221 unresolved = unresolvedArchives(targets.stream());
222 } while (!unresolved.isEmpty() && depth-- > 0);
223 }
224
225 /*
226 * Recursively analyze the class dependences
227 */
228 private void transitiveDeps(int depth) throws IOException {
229 Stream<Location> deps = archives.stream()
230 .flatMap(Archive::getDependencies);
231
232 Deque<Location> unresolved = deps.collect(Collectors.toCollection(LinkedList::new));
233 ConcurrentLinkedDeque<Location> deque = new ConcurrentLinkedDeque<>();
234 do {
235 Location target;
236 while ((target = unresolved.poll()) != null) {
237 if (finder.isParsed(target))
238 continue;
239
240 Archive archive = configuration.findClass(target).orElse(null);
241 if (archive != null && filter.include(archive)) {
242 archives.add(archive);
243
244 String name = target.getName();
245 Set<Location> targets = apiOnly
246 ? finder.parseExportedAPIs(archive, name)
247 : finder.parse(archive, name);
248
249 // build unresolved dependencies
250 targets.stream()
251 .filter(t -> !finder.isParsed(t))
252 .forEach(deque::add);
253 }
254 }
255 unresolved = deque;
256 deque = new ConcurrentLinkedDeque<>();
257 } while (!unresolved.isEmpty() && depth-- > 0);
258 }
259
260 // ----- for testing purpose -----
261
262 public static enum Info {
263 REQUIRES,
264 REQUIRES_TRANSITIVE,
265 EXPORTED_API,
266 MODULE_PRIVATE,
267 QUALIFIED_EXPORTED_API,
268 INTERNAL_API,
269 JDK_INTERNAL_API,
270 JDK_REMOVED_INTERNAL_API
271 }
272
273 public static class Node {
274 public final String name;
275 public final String source;
276 public final Info info;
277 Node(String name, Info info) {
278 this(name, name, info);
279 }
|
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
26 package com.sun.tools.jdeps;
27
28 import com.sun.tools.classfile.Dependency.Location;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Deque;
33 import java.util.LinkedHashSet;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Optional;
37 import java.util.Set;
38 import java.util.concurrent.ConcurrentLinkedDeque;
39 import java.util.stream.Collectors;
40 import java.util.stream.Stream;
41
42 import static com.sun.tools.jdeps.Analyzer.Type.CLASS;
43 import static com.sun.tools.jdeps.Analyzer.Type.VERBOSE;
44 import static com.sun.tools.jdeps.Module.trace;
45 import static java.util.stream.Collectors.*;
46
47 /**
48 * Dependency Analyzer.
49 *
50 * Type of filters:
51 * source filter: -include <pattern>
75 JdepsFilter filter,
76 JdepsWriter writer,
77 Analyzer.Type verbose,
78 boolean apiOnly) {
79 this.configuration = config;
80 this.filter = filter;
81 this.writer = writer;
82 this.verbose = verbose;
83 this.apiOnly = apiOnly;
84
85 this.finder = new DependencyFinder(config, filter);
86 this.analyzer = new Analyzer(configuration, verbose, filter);
87
88 // determine initial archives to be analyzed
89 this.rootArchives.addAll(configuration.initialArchives());
90
91 // if -include pattern is specified, add the matching archives on
92 // classpath to the root archives
93 if (filter.hasIncludePattern() || filter.hasTargetFilter()) {
94 configuration.getModules().values().stream()
95 .filter(source -> include(source) && filter.matches(source))
96 .forEach(this.rootArchives::add);
97 }
98
99 // class path archives
100 configuration.classPathArchives().stream()
101 .filter(filter::matches)
102 .forEach(this.rootArchives::add);
103
104 // Include the root modules for analysis
105 this.rootArchives.addAll(configuration.rootModules());
106
107 trace("analyze root archives: %s%n", this.rootArchives);
108 }
109
110 /*
111 * Perform runtime dependency analysis
112 */
113 public boolean run() throws IOException {
114 return run(false, 1);
115 }
145 // analyze the dependencies collected
146 analyzer.run(archives, finder.locationToArchive());
147
148 if (writer != null) {
149 writer.generateOutput(archives, analyzer);
150 }
151 } finally {
152 finder.shutdown();
153 }
154 return true;
155 }
156
157 /**
158 * Returns the archives for reporting that has matching dependences.
159 *
160 * If --require is set, they should be excluded.
161 */
162 Set<Archive> archives() {
163 if (filter.requiresFilter().isEmpty()) {
164 return archives.stream()
165 .filter(this::include)
166 .filter(Archive::hasDependences)
167 .collect(Collectors.toSet());
168 } else {
169 // use the archives that have dependences and not specified in --require
170 return archives.stream()
171 .filter(source -> !filter.requiresFilter().contains(source.getName()))
172 .filter(this::include)
173 .filter(source ->
174 source.getDependencies()
175 .map(finder::locationToArchive)
176 .anyMatch(a -> a != source))
177 .collect(Collectors.toSet());
178 }
179 }
180
181 /**
182 * Returns the dependences, either class name or package name
183 * as specified in the given verbose level.
184 */
185 Set<String> dependences() {
186 return analyzer.archives().stream()
187 .map(analyzer::dependences)
188 .flatMap(Set::stream)
189 .collect(Collectors.toSet());
190 }
191
192 /**
193 * Returns the archives that contains the given locations and
194 * not parsed and analyzed.
195 */
196 private Set<Archive> unresolvedArchives(Stream<Location> locations) {
197 return locations.filter(l -> !finder.isParsed(l))
198 .distinct()
199 .map(configuration::findClass)
200 .flatMap(Optional::stream)
201 .collect(toSet());
202 }
203
204 /*
205 * Recursively analyzes entire module/archives.
206 */
207 private void transitiveArchiveDeps(int depth) throws IOException {
208 Stream<Location> deps = archives.stream()
209 .flatMap(Archive::getDependencies);
210
211 // start with the unresolved archives
212 Set<Archive> unresolved = unresolvedArchives(deps);
213 do {
214 // parse all unresolved archives
215 Set<Location> targets = apiOnly
216 ? finder.parseExportedAPIs(unresolved.stream())
217 : finder.parse(unresolved.stream());
218 archives.addAll(unresolved);
219
220 // Add dependencies to the next batch for analysis
221 unresolved = unresolvedArchives(targets.stream());
222 } while (!unresolved.isEmpty() && depth-- > 0);
223 }
224
225 /*
226 * Recursively analyze the class dependences
227 */
228 private void transitiveDeps(int depth) throws IOException {
229 Stream<Location> deps = archives.stream()
230 .flatMap(Archive::getDependencies);
231
232 Collection<Module> modules = configuration.getModules().values();
233 Deque<Location> unresolved = deps.collect(Collectors.toCollection(LinkedList::new));
234 ConcurrentLinkedDeque<Location> deque = new ConcurrentLinkedDeque<>();
235 do {
236 Location target;
237 while ((target = unresolved.poll()) != null) {
238 if (finder.isParsed(target))
239 continue;
240
241 Archive archive = configuration.findClass(target).orElse(null);
242 if (archive != null) {
243 archives.add(archive);
244
245 String name = target.getName();
246 Set<Location> targets = apiOnly
247 ? finder.parseExportedAPIs(archive, name)
248 : finder.parse(archive, name);
249
250 // build unresolved dependencies
251 targets.stream()
252 .filter(t -> !finder.isParsed(t))
253 .forEach(deque::add);
254 }
255 }
256 unresolved = deque;
257 deque = new ConcurrentLinkedDeque<>();
258 } while (!unresolved.isEmpty() && depth-- > 0);
259 }
260
261 /*
262 * Tests if the given archive is requested for analysis.
263 * It includes the root modules specified in --module, --add-modules and
264 * dependences specified in --require
265 */
266 public boolean include(Archive source) {
267 Module module = source.getModule();
268 // skip system module by default
269 return !module.isSystem()
270 || configuration.rootModules().contains(source)
271 || filter.requiresFilter().contains(module.name());
272 }
273
274 // ----- for testing purpose -----
275
276 public static enum Info {
277 REQUIRES,
278 REQUIRES_TRANSITIVE,
279 EXPORTED_API,
280 MODULE_PRIVATE,
281 QUALIFIED_EXPORTED_API,
282 INTERNAL_API,
283 JDK_INTERNAL_API,
284 JDK_REMOVED_INTERNAL_API
285 }
286
287 public static class Node {
288 public final String name;
289 public final String source;
290 public final Info info;
291 Node(String name, Info info) {
292 this(name, name, info);
293 }
|