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 */ 25 package com.sun.tools.jdeps; 26 27 import com.sun.tools.classfile.Dependencies; 28 import com.sun.tools.classfile.Dependency; 29 import com.sun.tools.classfile.Dependency.Location; 30 31 import java.util.HashSet; 32 import java.util.Optional; 33 import java.util.Set; 34 import java.util.regex.Pattern; 35 import java.util.stream.Collectors; 36 import 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. --require specifies to match target dependence 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 */ 51 public class JdepsFilter implements Dependency.Filter, Analyzer.Filter { 52 53 public static final JdepsFilter DEFAULT_FILTER = 54 new JdepsFilter.Builder().filter(true, true).build(); 55 56 private final Dependency.Filter filter; 57 private final Pattern filterPattern; 58 private final boolean filterSamePackage; 59 private final boolean filterSameArchive; 60 private final boolean findJDKInternals; 61 private final Pattern includePattern; 62 private final Pattern includeSystemModules; 63 64 private final Set<String> requires; 65 66 private JdepsFilter(Dependency.Filter filter, 67 Pattern filterPattern, 68 boolean filterSamePackage, 69 boolean filterSameArchive, 70 boolean findJDKInternals, 71 Pattern includePattern, 72 Pattern includeSystemModules, 73 Set<String> requires) { 74 this.filter = filter; 75 this.filterPattern = filterPattern; 76 this.filterSamePackage = filterSamePackage; 77 this.filterSameArchive = filterSameArchive; 78 this.findJDKInternals = findJDKInternals; 79 this.includePattern = includePattern; 80 this.includeSystemModules = includeSystemModules; 81 this.requires = requires; 82 } 83 84 /** 85 * Tests if the given class matches the pattern given in the -include option 86 * 87 * @param cn fully-qualified name 88 */ 89 public boolean matches(String cn) { 90 if (includePattern == null) 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 -include option 101 * 102 * This method can be used to determine if the given source should eagerly 103 * be processed. 104 */ 105 public boolean matches(Archive source) { 106 if (includePattern != null) { 107 return source.reader().entries().stream() 108 .map(name -> name.replace('/', '.')) 109 .filter(name -> !name.equals("module-info.class")) 110 .anyMatch(this::matches); 111 } 112 return hasTargetFilter(); 113 } 114 115 public boolean include(Archive source) { 116 Module module = source.getModule(); 117 // skip system module by default; or if includeSystemModules is set 118 // only include the ones matching the pattern 119 return !module.isSystem() || (includeSystemModules != null && 120 includeSystemModules.matcher(module.name()).matches()); 121 } 122 123 public boolean hasIncludePattern() { 124 return includePattern != null || includeSystemModules != null; 125 } 126 127 public boolean hasTargetFilter() { 128 return filter != null; 129 } 130 131 public Set<String> requiresFilter() { 132 return requires; 133 } 134 135 // ----- Dependency.Filter ----- 136 137 @Override 138 public boolean accepts(Dependency d) { 139 if (d.getOrigin().equals(d.getTarget())) 140 return false; 141 142 // filter same package dependency 143 String pn = d.getTarget().getPackageName(); 144 if (filterSamePackage && d.getOrigin().getPackageName().equals(pn)) { 145 return false; 146 } 147 148 // filter if the target package matches the given filter 149 if (filterPattern != null && filterPattern.matcher(pn).matches()) { 150 return false; 151 } 152 153 // filter if the target matches the given filtered package name or regex 154 return filter != null ? filter.accepts(d) : true; 155 } 156 157 // ----- Analyzer.Filter ------ 158 159 /** 160 * Filter depending on the containing archive or module 161 */ 162 @Override 163 public boolean accepts(Location origin, Archive originArchive, 164 Location target, Archive targetArchive) { 165 if (findJDKInternals) { 166 // accepts target that is JDK class but not exported 167 Module module = targetArchive.getModule(); 168 return originArchive != targetArchive && 169 isJDKInternalPackage(module, target.getPackageName()); 170 } else if (filterSameArchive) { 171 // accepts origin and target that from different archive 172 return originArchive != targetArchive; 173 } 174 return true; 175 } 176 177 /** 178 * Tests if the package is an internal package of the given module. 179 */ 180 public boolean isJDKInternalPackage(Module module, String pn) { 181 if (module.isJDKUnsupported()) { 182 // its exported APIs are unsupported 183 return true; 184 } 185 186 return module.isJDK() && !module.isExported(pn); 187 } 188 189 @Override 190 public String toString() { 191 StringBuilder sb = new StringBuilder(); 192 sb.append("include pattern: ").append(includePattern).append("\n"); 193 sb.append("filter same archive: ").append(filterSameArchive).append("\n"); 194 sb.append("filter same package: ").append(filterSamePackage).append("\n"); 195 sb.append("requires: ").append(requires).append("\n"); 196 return sb.toString(); 197 } 198 199 public static class Builder { 200 static Pattern SYSTEM_MODULE_PATTERN = Pattern.compile("java\\..*|jdk\\..*|javafx\\..*"); 201 Pattern filterPattern; 202 Pattern regex; 203 boolean filterSamePackage; 204 boolean filterSameArchive; 205 boolean findJDKInterals; 206 // source filters 207 Pattern includePattern; 208 Pattern includeSystemModules; 209 Set<String> requires = new HashSet<>(); 210 Set<String> targetPackages = new HashSet<>(); 211 212 public Builder packages(Set<String> packageNames) { 213 this.targetPackages.addAll(packageNames); 214 return this; 215 } 216 public Builder regex(Pattern regex) { 217 this.regex = regex; 218 return this; 219 } 220 public Builder filter(Pattern regex) { 221 this.filterPattern = regex; 222 return this; 223 } 224 public Builder filter(boolean samePackage, boolean sameArchive) { 225 this.filterSamePackage = samePackage; 226 this.filterSameArchive = sameArchive; 227 return this; 228 } 229 public Builder requires(String name, Set<String> packageNames) { 230 this.requires.add(name); 231 this.targetPackages.addAll(packageNames); 232 233 includeIfSystemModule(name); 234 return this; 235 } 236 public Builder findJDKInternals(boolean value) { 237 this.findJDKInterals = value; 238 return this; 239 } 240 public Builder includePattern(Pattern regex) { 241 this.includePattern = regex; 242 return this; 243 } 244 public Builder includeSystemModules(Pattern regex) { 245 this.includeSystemModules = regex; 246 return this; 247 } 248 public Builder includeIfSystemModule(String name) { 249 if (includeSystemModules == null && 250 SYSTEM_MODULE_PATTERN.matcher(name).matches()) { 251 this.includeSystemModules = SYSTEM_MODULE_PATTERN; 252 } 253 return this; 254 } 255 256 public JdepsFilter build() { 257 Dependency.Filter filter = null; 258 if (regex != null) 259 filter = Dependencies.getRegexFilter(regex); 260 else if (!targetPackages.isEmpty()) { 261 filter = Dependencies.getPackageFilter(targetPackages, false); 262 } 263 return new JdepsFilter(filter, 264 filterPattern, 265 filterSamePackage, 266 filterSameArchive, 267 findJDKInterals, 268 includePattern, 269 includeSystemModules, 270 requires); 271 } 272 273 } 274 }