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 26 package java.io; 27 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.security.Security; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.Optional; 35 import java.util.function.Function; 36 37 import jdk.internal.misc.SharedSecrets; 38 39 /** 40 * Filter classes, array lengths, and graph metrics during deserialization. 41 * 42 * <p><strong>Warning: Deserialization of untrusted data is inherently dangerous 43 * and should be avoided. Untrusted data should be carefully validated according to the 44 * "Serialization and Deserialization" section of the 45 * {@extLink secure_coding_guidelines_javase Secure Coding Guidelines for Java SE}. 46 * {@extLink serialization_filter_guide Serialization Filtering} describes best 47 * practices for defensive use of serial filters. 48 * </strong></p> 49 * 50 * If set on an {@link ObjectInputStream}, the {@link #checkInput checkInput(FilterInfo)} 51 * method is called to validate classes, the length of each array, 52 * the number of objects being read from the stream, the depth of the graph, 53 * and the total number of bytes read from the stream. 54 * <p> 55 * A filter can be set via {@link ObjectInputStream#setObjectInputFilter setObjectInputFilter} 56 * for an individual ObjectInputStream. 57 * A filter can be set via {@link Config#setSerialFilter(ObjectInputFilter) Config.setSerialFilter} 58 * to affect every {@code ObjectInputStream} that does not otherwise set a filter. 59 * <p> 60 * A filter determines whether the arguments are {@link Status#ALLOWED ALLOWED} 61 * or {@link Status#REJECTED REJECTED} and should return the appropriate status. 62 * If the filter cannot determine the status it should return 63 * {@link Status#UNDECIDED UNDECIDED}. 64 * Filters should be designed for the specific use case and expected types. 65 * A filter designed for a particular use may be passed a class that is outside 66 * of the scope of the filter. If the purpose of the filter is to black-list classes 67 * then it can reject a candidate class that matches and report UNDECIDED for others. 68 * A filter may be called with class equals {@code null}, {@code arrayLength} equal -1, 69 * the depth, number of references, and stream size and return a status 70 * that reflects only one or only some of the values. 71 * This allows a filter to specific about the choice it is reporting and 72 * to use other filters without forcing either allowed or rejected status. 73 * 74 * <p> 75 * Typically, a custom filter should check if a process-wide filter 76 * is configured and defer to it if so. For example, 77 * <pre>{@code 78 * ObjectInputFilter.Status checkInput(FilterInfo info) { 79 * ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); 80 * if (serialFilter != null) { 81 * ObjectInputFilter.Status status = serialFilter.checkInput(info); 82 * if (status != ObjectInputFilter.Status.UNDECIDED) { 83 * // The process-wide filter overrides this filter 84 * return status; 85 * } 86 * } 87 * if (info.serialClass() != null && 88 * Remote.class.isAssignableFrom(info.serialClass())) { 89 * return Status.REJECTED; // Do not allow Remote objects 90 * } 91 * return Status.UNDECIDED; 92 * } 93 *}</pre> 94 * <p> 95 * Unless otherwise noted, passing a {@code null} argument to a 96 * method in this interface and its nested classes will cause a 97 * {@link NullPointerException} to be thrown. 98 * 99 * @see ObjectInputStream#setObjectInputFilter(ObjectInputFilter) 100 * @since 9 101 */ 102 @FunctionalInterface 103 public interface ObjectInputFilter { 104 105 /** 106 * Check the class, array length, number of object references, depth, 107 * stream size, and other available filtering information. 108 * Implementations of this method check the contents of the object graph being created 109 * during deserialization. The filter returns {@link Status#ALLOWED Status.ALLOWED}, 110 * {@link Status#REJECTED Status.REJECTED}, or {@link Status#UNDECIDED Status.UNDECIDED}. 111 * 112 * @param filterInfo provides information about the current object being deserialized, 113 * if any, and the status of the {@link ObjectInputStream} 114 * @return {@link Status#ALLOWED Status.ALLOWED} if accepted, 115 * {@link Status#REJECTED Status.REJECTED} if rejected, 116 * {@link Status#UNDECIDED Status.UNDECIDED} if undecided. 117 */ 118 Status checkInput(FilterInfo filterInfo); 119 120 /** 121 * FilterInfo provides access to information about the current object 122 * being deserialized and the status of the {@link ObjectInputStream}. 123 * @since 9 124 */ 125 interface FilterInfo { 126 /** 127 * The class of an object being deserialized. 128 * For arrays, it is the array type. 129 * For example, the array class name of a 2 dimensional array of strings is 130 * "{@code [[Ljava.lang.String;}". 131 * To check the array's element type, iteratively use 132 * {@link Class#getComponentType() Class.getComponentType} while the result 133 * is an array and then check the class. 134 * The {@code serialClass is null} in the case where a new object is not being 135 * created and to give the filter a chance to check the depth, number of 136 * references to existing objects, and the stream size. 137 * 138 * @return class of an object being deserialized; may be null 139 */ 140 Class<?> serialClass(); 141 142 /** 143 * The number of array elements when deserializing an array of the class. 144 * 145 * @return the non-negative number of array elements when deserializing 146 * an array of the class, otherwise -1 147 */ 148 long arrayLength(); 149 150 /** 151 * The current depth. 152 * The depth starts at {@code 1} and increases for each nested object and 153 * decrements when each nested object returns. 154 * 155 * @return the current depth 156 */ 157 long depth(); 158 159 /** 160 * The current number of object references. 161 * 162 * @return the non-negative current number of object references 163 */ 164 long references(); 165 166 /** 167 * The current number of bytes consumed. 168 * @implSpec {@code streamBytes} is implementation specific 169 * and may not be directly related to the object in the stream 170 * that caused the callback. 171 * 172 * @return the non-negative current number of bytes consumed 173 */ 174 long streamBytes(); 175 } 176 177 /** 178 * The status of a check on the class, array length, number of references, 179 * depth, and stream size. 180 * 181 * @since 9 182 */ 183 enum Status { 184 /** 185 * The status is undecided, not allowed and not rejected. 186 */ 187 UNDECIDED, 188 /** 189 * The status is allowed. 190 */ 191 ALLOWED, 192 /** 193 * The status is rejected. 194 */ 195 REJECTED; 196 } 197 198 /** 199 * A utility class to set and get the process-wide filter or create a filter 200 * from a pattern string. If a process-wide filter is set, it will be 201 * used for each {@link ObjectInputStream} that does not set its own filter. 202 * <p> 203 * When setting the filter, it should be stateless and idempotent, 204 * reporting the same result when passed the same arguments. 205 * <p> 206 * The filter is configured during the initialization of the {@code ObjectInputFilter.Config} 207 * class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}. 208 * If the system property {@code jdk.serialFilter} is defined, it is used 209 * to configure the filter. 210 * If the system property is not defined, and the {@link java.security.Security} 211 * property {@code jdk.serialFilter} is defined then it is used to configure the filter. 212 * Otherwise, the filter is not configured during initialization. 213 * The syntax for each property is the same as for the 214 * {@link #createFilter(String) createFilter} method. 215 * If a filter is not configured, it can be set with 216 * {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}. 217 * 218 * @since 9 219 */ 220 final class Config { 221 /* No instances. */ 222 private Config() {} 223 224 /** 225 * Lock object for process-wide filter. 226 */ 227 private final static Object serialFilterLock = new Object(); 228 229 /** 230 * Debug: Logger 231 */ 232 private final static System.Logger configLog; 233 234 /** 235 * Logger for debugging. 236 */ 237 static void filterLog(System.Logger.Level level, String msg, Object... args) { 238 if (configLog != null) { 239 configLog.log(level, msg, args); 240 } 241 } 242 243 /** 244 * The name for the process-wide deserialization filter. 245 * Used as a system property and a java.security.Security property. 246 */ 247 private final static String SERIAL_FILTER_PROPNAME = "jdk.serialFilter"; 248 249 /** 250 * The process-wide filter; may be null. 251 * Lookup the filter in java.security.Security or 252 * the system property. 253 */ 254 private final static ObjectInputFilter configuredFilter; 255 256 static { 257 configuredFilter = AccessController 258 .doPrivileged((PrivilegedAction<ObjectInputFilter>) () -> { 259 String props = System.getProperty(SERIAL_FILTER_PROPNAME); 260 if (props == null) { 261 props = Security.getProperty(SERIAL_FILTER_PROPNAME); 262 } 263 if (props != null) { 264 System.Logger log = 265 System.getLogger("java.io.serialization"); 266 log.log(System.Logger.Level.INFO, 267 "Creating serialization filter from {0}", props); 268 try { 269 return createFilter(props); 270 } catch (RuntimeException re) { 271 log.log(System.Logger.Level.ERROR, 272 "Error configuring filter: {0}", re); 273 } 274 } 275 return null; 276 }); 277 configLog = (configuredFilter != null) ? System.getLogger("java.io.serialization") : null; 278 279 // Setup shared secrets for RegistryImpl to use. 280 SharedSecrets.setJavaObjectInputFilterAccess(Config::createFilter2); 281 } 282 283 /** 284 * Current configured filter. 285 */ 286 private static ObjectInputFilter serialFilter = configuredFilter; 287 288 /** 289 * Returns the process-wide serialization filter or {@code null} if not configured. 290 * 291 * @return the process-wide serialization filter or {@code null} if not configured 292 */ 293 public static ObjectInputFilter getSerialFilter() { 294 synchronized (serialFilterLock) { 295 return serialFilter; 296 } 297 } 298 299 /** 300 * Set the process-wide filter if it has not already been configured or set. 301 * 302 * @param filter the serialization filter to set as the process-wide filter; not null 303 * @throws SecurityException if there is security manager and the 304 * {@code SerializablePermission("serialFilter")} is not granted 305 * @throws IllegalStateException if the filter has already been set {@code non-null} 306 */ 307 public static void setSerialFilter(ObjectInputFilter filter) { 308 Objects.requireNonNull(filter, "filter"); 309 SecurityManager sm = System.getSecurityManager(); 310 if (sm != null) { 311 sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION); 312 } 313 synchronized (serialFilterLock) { 314 if (serialFilter != null) { 315 throw new IllegalStateException("Serial filter can only be set once"); 316 } 317 serialFilter = filter; 318 } 319 } 320 321 /** 322 * Returns an ObjectInputFilter from a string of patterns. 323 * <p> 324 * Patterns are separated by ";" (semicolon). Whitespace is significant and 325 * is considered part of the pattern. 326 * If a pattern includes an equals assignment, "{@code =}" it sets a limit. 327 * If a limit appears more than once the last value is used. 328 * <ul> 329 * <li>maxdepth={@code value} - the maximum depth of a graph</li> 330 * <li>maxrefs={@code value} - the maximum number of internal references</li> 331 * <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li> 332 * <li>maxarray={@code value} - the maximum array length allowed</li> 333 * </ul> 334 * <p> 335 * Other patterns match or reject class or package name 336 * as returned from {@link Class#getName() Class.getName()} and 337 * if an optional module name is present 338 * {@link Module#getName() class.getModule().getName()}. 339 * Note that for arrays the element type is used in the pattern, 340 * not the array type. 341 * <ul> 342 * <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched; 343 * otherwise the class is allowed if the pattern matches. 344 * <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name; 345 * if the module name matches the module name of the class then 346 * the remaining pattern is matched with the class name. 347 * If there is no "/", the module name is not compared. 348 * <li>If the pattern ends with ".**" it matches any class in the package and all subpackages. 349 * <li>If the pattern ends with ".*" it matches any class in the package. 350 * <li>If the pattern ends with "*", it matches any class with the pattern as a prefix. 351 * <li>If the pattern is equal to the class name, it matches. 352 * <li>Otherwise, the pattern is not matched. 353 * </ul> 354 * <p> 355 * The resulting filter performs the limit checks and then 356 * tries to match the class, if any. If any of the limits are exceeded, 357 * the filter returns {@link Status#REJECTED Status.REJECTED}. 358 * If the class is an array type, the class to be matched is the element type. 359 * Arrays of any number of dimensions are treated the same as the element type. 360 * For example, a pattern of "{@code !example.Foo}", 361 * rejects creation of any instance or array of {@code example.Foo}. 362 * The first pattern that matches, working from left to right, determines 363 * the {@link Status#ALLOWED Status.ALLOWED} 364 * or {@link Status#REJECTED Status.REJECTED} result. 365 * If the limits are not exceeded and no pattern matches the class, 366 * the result is {@link Status#UNDECIDED Status.UNDECIDED}. 367 * 368 * @param pattern the pattern string to parse; not null 369 * @return a filter to check a class being deserialized; 370 * {@code null} if no patterns 371 * @throws IllegalArgumentException if the pattern string is illegal or 372 * malformed and cannot be parsed. 373 * In particular, if any of the following is true: 374 * <ul> 375 * <li> if a limit is missing the name or the name is not one of 376 * "maxdepth", "maxrefs", "maxbytes", or "maxarray" 377 * <li> if the value of the limit can not be parsed by 378 * {@link Long#parseLong Long.parseLong} or is negative 379 * <li> if the pattern contains "/" and the module name is missing 380 * or the remaining pattern is empty 381 * <li> if the package is missing for ".*" and ".**" 382 * </ul> 383 */ 384 public static ObjectInputFilter createFilter(String pattern) { 385 Objects.requireNonNull(pattern, "pattern"); 386 return Global.createFilter(pattern, true); 387 } 388 389 /** 390 * Returns an ObjectInputFilter from a string of patterns that 391 * checks only the length for arrays, not the component type. 392 * 393 * @param pattern the pattern string to parse; not null 394 * @return a filter to check a class being deserialized; 395 * {@code null} if no patterns 396 */ 397 static ObjectInputFilter createFilter2(String pattern) { 398 Objects.requireNonNull(pattern, "pattern"); 399 return Global.createFilter(pattern, false); 400 } 401 402 /** 403 * Implementation of ObjectInputFilter that performs the checks of 404 * the process-wide serialization filter. If configured, it will be 405 * used for all ObjectInputStreams that do not set their own filters. 406 * 407 */ 408 final static class Global implements ObjectInputFilter { 409 /** 410 * The pattern used to create the filter. 411 */ 412 private final String pattern; 413 /** 414 * The list of class filters. 415 */ 416 private final List<Function<Class<?>, Status>> filters; 417 /** 418 * Maximum allowed bytes in the stream. 419 */ 420 private long maxStreamBytes; 421 /** 422 * Maximum depth of the graph allowed. 423 */ 424 private long maxDepth; 425 /** 426 * Maximum number of references in a graph. 427 */ 428 private long maxReferences; 429 /** 430 * Maximum length of any array. 431 */ 432 private long maxArrayLength; 433 /** 434 * True to check the component type for arrays. 435 */ 436 private final boolean checkComponentType; 437 438 /** 439 * Returns an ObjectInputFilter from a string of patterns. 440 * 441 * @param pattern the pattern string to parse 442 * @param checkComponentType true if the filter should check 443 * the component type of arrays 444 * @return a filter to check a class being deserialized; 445 * {@code null} if no patterns 446 * @throws IllegalArgumentException if the parameter is malformed 447 * if the pattern is missing the name, the long value 448 * is not a number or is negative. 449 */ 450 static ObjectInputFilter createFilter(String pattern, boolean checkComponentType) { 451 try { 452 return new Global(pattern, checkComponentType); 453 } catch (UnsupportedOperationException uoe) { 454 // no non-empty patterns 455 return null; 456 } 457 } 458 459 /** 460 * Construct a new filter from the pattern String. 461 * 462 * @param pattern a pattern string of filters 463 * @param checkComponentType true if the filter should check 464 * the component type of arrays 465 * @throws IllegalArgumentException if the pattern is malformed 466 * @throws UnsupportedOperationException if there are no non-empty patterns 467 */ 468 private Global(String pattern, boolean checkComponentType) { 469 boolean hasLimits = false; 470 this.pattern = pattern; 471 this.checkComponentType = checkComponentType; 472 473 maxArrayLength = Long.MAX_VALUE; // Default values are unlimited 474 maxDepth = Long.MAX_VALUE; 475 maxReferences = Long.MAX_VALUE; 476 maxStreamBytes = Long.MAX_VALUE; 477 478 String[] patterns = pattern.split(";"); 479 filters = new ArrayList<>(patterns.length); 480 for (int i = 0; i < patterns.length; i++) { 481 String p = patterns[i]; 482 int nameLen = p.length(); 483 if (nameLen == 0) { 484 continue; 485 } 486 if (parseLimit(p)) { 487 // If the pattern contained a limit setting, i.e. type=value 488 hasLimits = true; 489 continue; 490 } 491 boolean negate = p.charAt(0) == '!'; 492 int poffset = negate ? 1 : 0; 493 494 // isolate module name, if any 495 int slash = p.indexOf('/', poffset); 496 if (slash == poffset) { 497 throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\""); 498 } 499 final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null; 500 poffset = (slash >= 0) ? slash + 1 : poffset; 501 502 final Function<Class<?>, Status> patternFilter; 503 if (p.endsWith("*")) { 504 // Wildcard cases 505 if (p.endsWith(".*")) { 506 // Pattern is a package name with a wildcard 507 final String pkg = p.substring(poffset, nameLen - 2); 508 if (pkg.isEmpty()) { 509 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 510 } 511 if (negate) { 512 // A Function that fails if the class starts with the pattern, otherwise don't care 513 patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED; 514 } else { 515 // A Function that succeeds if the class starts with the pattern, otherwise don't care 516 patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED; 517 } 518 } else if (p.endsWith(".**")) { 519 // Pattern is a package prefix with a double wildcard 520 final String pkgs = p.substring(poffset, nameLen - 2); 521 if (pkgs.length() < 2) { 522 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 523 } 524 if (negate) { 525 // A Function that fails if the class starts with the pattern, otherwise don't care 526 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED; 527 } else { 528 // A Function that succeeds if the class starts with the pattern, otherwise don't care 529 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED; 530 } 531 } else { 532 // Pattern is a classname (possibly empty) with a trailing wildcard 533 final String className = p.substring(poffset, nameLen - 1); 534 if (negate) { 535 // A Function that fails if the class starts with the pattern, otherwise don't care 536 patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED; 537 } else { 538 // A Function that succeeds if the class starts with the pattern, otherwise don't care 539 patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED; 540 } 541 } 542 } else { 543 final String name = p.substring(poffset); 544 if (name.isEmpty()) { 545 throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\""); 546 } 547 // Pattern is a class name 548 if (negate) { 549 // A Function that fails if the class equals the pattern, otherwise don't care 550 patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED; 551 } else { 552 // A Function that succeeds if the class equals the pattern, otherwise don't care 553 patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED; 554 } 555 } 556 // If there is a moduleName, combine the module name check with the package/class check 557 if (moduleName == null) { 558 filters.add(patternFilter); 559 } else { 560 filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED); 561 } 562 } 563 if (filters.isEmpty() && !hasLimits) { 564 throw new UnsupportedOperationException("no non-empty patterns"); 565 } 566 } 567 568 /** 569 * Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences. 570 * 571 * @param pattern a string with a type name, '=' and a value 572 * @return {@code true} if a limit was parsed, else {@code false} 573 * @throws IllegalArgumentException if the pattern is missing 574 * the name, the Long value is not a number or is negative. 575 */ 576 private boolean parseLimit(String pattern) { 577 int eqNdx = pattern.indexOf('='); 578 if (eqNdx < 0) { 579 // not a limit pattern 580 return false; 581 } 582 String valueString = pattern.substring(eqNdx + 1); 583 if (pattern.startsWith("maxdepth=")) { 584 maxDepth = parseValue(valueString); 585 } else if (pattern.startsWith("maxarray=")) { 586 maxArrayLength = parseValue(valueString); 587 } else if (pattern.startsWith("maxrefs=")) { 588 maxReferences = parseValue(valueString); 589 } else if (pattern.startsWith("maxbytes=")) { 590 maxStreamBytes = parseValue(valueString); 591 } else { 592 throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx)); 593 } 594 return true; 595 } 596 597 /** 598 * Parse the value of a limit and check that it is non-negative. 599 * @param string inputstring 600 * @return the parsed value 601 * @throws IllegalArgumentException if parsing the value fails or the value is negative 602 */ 603 private static long parseValue(String string) throws IllegalArgumentException { 604 // Parse a Long from after the '=' to the end 605 long value = Long.parseLong(string); 606 if (value < 0) { 607 throw new IllegalArgumentException("negative limit: " + string); 608 } 609 return value; 610 } 611 612 /** 613 * {@inheritDoc} 614 */ 615 @Override 616 public Status checkInput(FilterInfo filterInfo) { 617 if (filterInfo.references() < 0 618 || filterInfo.depth() < 0 619 || filterInfo.streamBytes() < 0 620 || filterInfo.references() > maxReferences 621 || filterInfo.depth() > maxDepth 622 || filterInfo.streamBytes() > maxStreamBytes) { 623 return Status.REJECTED; 624 } 625 626 Class<?> clazz = filterInfo.serialClass(); 627 if (clazz != null) { 628 if (clazz.isArray()) { 629 if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) { 630 // array length is too big 631 return Status.REJECTED; 632 } 633 if (!checkComponentType) { 634 // As revised; do not check the component type for arrays 635 return Status.UNDECIDED; 636 } 637 do { 638 // Arrays are decided based on the component type 639 clazz = clazz.getComponentType(); 640 } while (clazz.isArray()); 641 } 642 643 if (clazz.isPrimitive()) { 644 // Primitive types are undecided; let someone else decide 645 return Status.UNDECIDED; 646 } else { 647 // Find any filter that allowed or rejected the class 648 final Class<?> cl = clazz; 649 Optional<Status> status = filters.stream() 650 .map(f -> f.apply(cl)) 651 .filter(p -> p != Status.UNDECIDED) 652 .findFirst(); 653 return status.orElse(Status.UNDECIDED); 654 } 655 } 656 return Status.UNDECIDED; 657 } 658 659 /** 660 * Returns {@code true} if the class is in the package. 661 * 662 * @param c a class 663 * @param pkg a package name 664 * @return {@code true} if the class is in the package, 665 * otherwise {@code false} 666 */ 667 private static boolean matchesPackage(Class<?> c, String pkg) { 668 return pkg.equals(c.getPackageName()); 669 } 670 671 /** 672 * Returns the pattern used to create this filter. 673 * @return the pattern used to create this filter 674 */ 675 @Override 676 public String toString() { 677 return pattern; 678 } 679 } 680 } 681 }