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