JdepsFilter.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2016, 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 */
25package com.sun.tools.jdeps;
26
27import com.sun.tools.classfile.Dependencies;
28import com.sun.tools.classfile.Dependency;
29import com.sun.tools.classfile.Dependency.Location;
30
31import java.util.HashSet;
32import java.util.Optional;
33import java.util.Set;
34import java.util.regex.Pattern;
35import java.util.stream.Collectors;
36import java.util.stream.Stream;
37
38/*
39 * Filter configured based on the input jdeps option
40 * 1. -p and -regex to match target dependencies
41 * 2. -filter:package to filter out same-package dependencies
42 *    This filter is applied when jdeps parses the class files
43 *    and filtered dependencies are not stored in the Analyzer.
44 * 3. -module specifies to match target dependencies from the given module
45 *    This gets expanded into package lists to be filtered.
46 * 4. -filter:archive to filter out same-archive dependencies
47 *    This filter is applied later in the Analyzer as the
48 *    containing archive of a target class may not be known until
49 *    the entire archive
50 */
51class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
52    private final Dependency.Filter filter;
53    private final Pattern filterPattern;
54    private final boolean filterSamePackage;
55    private final boolean filterSameArchive;
56    private final boolean findJDKInternals;
57    private final Pattern includePattern;
58    private final Set<String> includePackages;
59    private final Set<String> excludeModules;
60
61    private JdepsFilter(Dependency.Filter filter,
62                        Pattern filterPattern,
63                        boolean filterSamePackage,
64                        boolean filterSameArchive,
65                        boolean findJDKInternals,
66                        Pattern includePattern,
67                        Set<String> includePackages,
68                        Set<String> excludeModules) {
69        this.filter = filter;
70        this.filterPattern = filterPattern;
71        this.filterSamePackage = filterSamePackage;
72        this.filterSameArchive = filterSameArchive;
73        this.findJDKInternals = findJDKInternals;
74        this.includePattern = includePattern;
75        this.includePackages = includePackages;
76        this.excludeModules = excludeModules;
77    }
78
79    /**
80     * Tests if the given class matches the pattern given in the -include option
81     *
82     * @param cn fully-qualified name
83     */
84    public boolean matches(String cn) {
85        if (includePackages.isEmpty() && includePattern == null)
86            return true;
87
88        int i = cn.lastIndexOf('.');
89        String pn = i > 0 ? cn.substring(0, i) : "";
90        if (includePackages.contains(pn))
91            return true;
92
93        if (includePattern != null)
94            return includePattern.matcher(cn).matches();
95
96        return false;
97    }
98
99    /**
100     * Tests if the given source includes classes specified in includePattern
101     * or includePackages filters.
102     *
103     * This method can be used to determine if the given source should eagerly
104     * be processed.
105     */
106    public boolean matches(Archive source) {
107        if (!includePackages.isEmpty() && source.getModule().isNamed()) {
108            boolean found = source.getModule().packages()
109                                  .stream()
110                                  .filter(pn -> includePackages.contains(pn))
111                                  .findAny().isPresent();
112            if (found)
113                return true;
114        }
115        if (!includePackages.isEmpty() || includePattern != null) {
116            return source.reader().entries()
117                         .stream()
118                         .map(name -> name.replace('/', '.'))
119                         .filter(this::matches)
120                         .findAny().isPresent();
121        }
122        return false;
123    }
124
125    // ----- Dependency.Filter -----
126
127    @Override
128    public boolean accepts(Dependency d) {
129        if (d.getOrigin().equals(d.getTarget()))
130            return false;
131
132        // filter same package dependency
133        String pn = d.getTarget().getPackageName();
134        if (filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
135            return false;
136        }
137
138        // filter if the target package matches the given filter
139        if (filterPattern != null && filterPattern.matcher(pn).matches()) {
140            return false;
141        }
142
143        // filter if the target matches the given filtered package name or regex
144        return filter != null ? filter.accepts(d) : true;
145    }
146
147    // ----- Analyzer.Filter ------
148
149    /**
150     * Filter depending on the containing archive or module
151     */
152    @Override
153    public boolean accepts(Location origin, Archive originArchive,
154                           Location target, Archive targetArchive) {
155        if (findJDKInternals) {
156            // accepts target that is JDK class but not exported
157            Module module = targetArchive.getModule();
158            return originArchive != targetArchive &&
159                    module.isJDK() && !module.isExported(target.getPackageName());
160        } else if (filterSameArchive) {
161            // accepts origin and target that from different archive
162            return originArchive != targetArchive;
163        }
164        return true;
165    }
166
167    /**
168     * Returns true if dependency should be recorded for the given source.
169     */
170    public boolean accept(Archive source) {
171        return !excludeModules.contains(source.getName());
172    }
173
174    @Override
175    public String toString() {
176        StringBuilder sb = new StringBuilder();
177        sb.append("exclude modules: ")
178          .append(excludeModules.stream().sorted().collect(Collectors.joining(",")))
179          .append("\n");
180        sb.append("filter same archive: ").append(filterSameArchive).append("\n");
181        sb.append("filter same package: ").append(filterSamePackage).append("\n");
182        return sb.toString();
183    }
184
185    static class Builder {
186        Dependency.Filter filter;
187        Pattern filterPattern;
188        boolean filterSamePackage;
189        boolean filterSameArchive;
190        boolean findJDKInterals;
191        // source filters
192        Pattern includePattern;
193        Set<String> includePackages = new HashSet<>();
194        Set<String> includeModules = new HashSet<>();
195        Set<String> excludeModules = new HashSet<>();
196
197        public Builder packages(Set<String> packageNames) {
198            this.filter = Dependencies.getPackageFilter(packageNames, false);
199            return this;
200        }
201        public Builder regex(Pattern regex) {
202            this.filter = Dependencies.getRegexFilter(regex);
203            return this;
204        }
205        public Builder filter(Pattern regex) {
206            this.filterPattern = regex;
207            return this;
208        }
209        public Builder filter(boolean samePackage, boolean sameArchive) {
210            this.filterSamePackage = samePackage;
211            this.filterSameArchive = sameArchive;
212            return this;
213        }
214        public Builder findJDKInternals(boolean value) {
215            this.findJDKInterals = value;
216            return this;
217        }
218        public Builder includePattern(Pattern regex) {
219            this.includePattern = regex;
220            return this;
221        }
222        public Builder includePackage(String pn) {
223            this.includePackages.add(pn);
224            return this;
225        }
226        public Builder includeModules(Set<String> includes) {
227            this.includeModules.addAll(includes);
228            return this;
229        }
230        public Builder excludeModules(Set<String> excludes) {
231            this.excludeModules.addAll(excludes);
232            return this;
233        }
234
235        JdepsFilter build() {
236            return new JdepsFilter(filter,
237                                   filterPattern,
238                                   filterSamePackage,
239                                   filterSameArchive,
240                                   findJDKInterals,
241                                   includePattern,
242                                   includePackages,
243                                   excludeModules.stream()
244                                        .filter(mn -> !includeModules.contains(mn))
245                                        .collect(Collectors.toSet()));
246        }
247
248    }
249}
250