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 volatile 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 return serialFilter; 295 } 296 297 /** 298 * Set the process-wide filter if it has not already been configured or set. 299 * 300 * @param filter the serialization filter to set as the process-wide filter; not null 301 * @throws SecurityException if there is security manager and the 302 * {@code SerializablePermission("serialFilter")} is not granted 303 * @throws IllegalStateException if the filter has already been set {@code non-null} 304 */ 305 public static void setSerialFilter(ObjectInputFilter filter) { 306 Objects.requireNonNull(filter, "filter"); 307 SecurityManager sm = System.getSecurityManager(); 308 if (sm != null) { 309 sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION); 310 } 311 synchronized (serialFilterLock) { 312 if (serialFilter != null) { 313 throw new IllegalStateException("Serial filter can only be set once"); 314 } 315 serialFilter = filter; 316 } 317 } 318 319 /** 320 * Returns an ObjectInputFilter from a string of patterns. 321 * <p> 322 * Patterns are separated by ";" (semicolon). Whitespace is significant and 323 * is considered part of the pattern. 324 * If a pattern includes an equals assignment, "{@code =}" it sets a limit. 325 * If a limit appears more than once the last value is used. 326 * <ul> 327 * <li>maxdepth={@code value} - the maximum depth of a graph</li> 328 * <li>maxrefs={@code value} - the maximum number of internal references</li> 329 * <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li> 330 * <li>maxarray={@code value} - the maximum array length allowed</li> 331 * </ul> 332 * <p> 333 * Other patterns match or reject class or package name 334 * as returned from {@link Class#getName() Class.getName()} and 335 * if an optional module name is present 336 * {@link Module#getName() class.getModule().getName()}. 337 * Note that for arrays the element type is used in the pattern, 338 * not the array type. 339 * <ul> 340 * <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched; 341 * otherwise the class is allowed if the pattern matches. 342 * <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name; 343 * if the module name matches the module name of the class then 344 * the remaining pattern is matched with the class name. 345 * If there is no "/", the module name is not compared. 346 * <li>If the pattern ends with ".**" it matches any class in the package and all subpackages. 347 * <li>If the pattern ends with ".*" it matches any class in the package. 348 * <li>If the pattern ends with "*", it matches any class with the pattern as a prefix. 349 * <li>If the pattern is equal to the class name, it matches. 350 * <li>Otherwise, the pattern is not matched. 351 * </ul> 352 * <p> 353 * The resulting filter performs the limit checks and then 354 * tries to match the class, if any. If any of the limits are exceeded, 355 * the filter returns {@link Status#REJECTED Status.REJECTED}. 356 * If the class is an array type, the class to be matched is the element type. 357 * Arrays of any number of dimensions are treated the same as the element type. 358 * For example, a pattern of "{@code !example.Foo}", 359 * rejects creation of any instance or array of {@code example.Foo}. 360 * The first pattern that matches, working from left to right, determines 361 * the {@link Status#ALLOWED Status.ALLOWED} 362 * or {@link Status#REJECTED Status.REJECTED} result. 363 * If the limits are not exceeded and no pattern matches the class, 364 * the result is {@link Status#UNDECIDED Status.UNDECIDED}. 365 * 366 * @param pattern the pattern string to parse; not null 367 * @return a filter to check a class being deserialized; 368 * {@code null} if no patterns 369 * @throws IllegalArgumentException if the pattern string is illegal or 370 * malformed and cannot be parsed. 371 * In particular, if any of the following is true: 372 * <ul> 373 * <li> if a limit is missing the name or the name is not one of 374 * "maxdepth", "maxrefs", "maxbytes", or "maxarray" 375 * <li> if the value of the limit can not be parsed by 376 * {@link Long#parseLong Long.parseLong} or is negative 377 * <li> if the pattern contains "/" and the module name is missing 378 * or the remaining pattern is empty 379 * <li> if the package is missing for ".*" and ".**" 380 * </ul> 381 */ 382 public static ObjectInputFilter createFilter(String pattern) { 383 Objects.requireNonNull(pattern, "pattern"); 384 return Global.createFilter(pattern, true); 385 } 386 387 /** 388 * Returns an ObjectInputFilter from a string of patterns that 389 * checks only the length for arrays, not the component type. 390 * 391 * @param pattern the pattern string to parse; not null 392 * @return a filter to check a class being deserialized; 393 * {@code null} if no patterns 394 */ 395 static ObjectInputFilter createFilter2(String pattern) { 396 Objects.requireNonNull(pattern, "pattern"); 397 return Global.createFilter(pattern, false); 398 } 399 400 /** 401 * Implementation of ObjectInputFilter that performs the checks of 402 * the process-wide serialization filter. If configured, it will be 403 * used for all ObjectInputStreams that do not set their own filters. 404 * 405 */ 406 final static class Global implements ObjectInputFilter { 407 /** 408 * The pattern used to create the filter. 409 */ 410 private final String pattern; 411 /** 412 * The list of class filters. 413 */ 414 private final List<Function<Class<?>, Status>> filters; 415 /** 416 * Maximum allowed bytes in the stream. 417 */ 418 private long maxStreamBytes; 419 /** 420 * Maximum depth of the graph allowed. 421 */ 422 private long maxDepth; 423 /** 424 * Maximum number of references in a graph. 425 */ 426 private long maxReferences; 427 /** 428 * Maximum length of any array. 429 */ 430 private long maxArrayLength; 431 /** 432 * True to check the component type for arrays. 433 */ 434 private final boolean checkComponentType; 435 436 /** 437 * Returns an ObjectInputFilter from a string of patterns. 438 * 439 * @param pattern the pattern string to parse 440 * @param checkComponentType true if the filter should check 441 * the component type of arrays 442 * @return a filter to check a class being deserialized; 443 * {@code null} if no patterns 444 * @throws IllegalArgumentException if the parameter is malformed 445 * if the pattern is missing the name, the long value 446 * is not a number or is negative. 447 */ 448 static ObjectInputFilter createFilter(String pattern, boolean checkComponentType) { 449 try { 450 return new Global(pattern, checkComponentType); 451 } catch (UnsupportedOperationException uoe) { 452 // no non-empty patterns 453 return null; 454 } 455 } 456 457 /** 458 * Construct a new filter from the pattern String. 459 * 460 * @param pattern a pattern string of filters 461 * @param checkComponentType true if the filter should check 462 * the component type of arrays 463 * @throws IllegalArgumentException if the pattern is malformed 464 * @throws UnsupportedOperationException if there are no non-empty patterns 465 */ 466 private Global(String pattern, boolean checkComponentType) { 467 boolean hasLimits = false; 468 this.pattern = pattern; 469 this.checkComponentType = checkComponentType; 470 471 maxArrayLength = Long.MAX_VALUE; // Default values are unlimited 472 maxDepth = Long.MAX_VALUE; 473 maxReferences = Long.MAX_VALUE; 474 maxStreamBytes = Long.MAX_VALUE; 475 476 String[] patterns = pattern.split(";"); 477 filters = new ArrayList<>(patterns.length); 478 for (int i = 0; i < patterns.length; i++) { 479 String p = patterns[i]; 480 int nameLen = p.length(); 481 if (nameLen == 0) { 482 continue; 483 } 484 if (parseLimit(p)) { 485 // If the pattern contained a limit setting, i.e. type=value 486 hasLimits = true; 487 continue; 488 } 489 boolean negate = p.charAt(0) == '!'; 490 int poffset = negate ? 1 : 0; 491 492 // isolate module name, if any 493 int slash = p.indexOf('/', poffset); 494 if (slash == poffset) { 495 throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\""); 496 } 497 final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null; 498 poffset = (slash >= 0) ? slash + 1 : poffset; 499 500 final Function<Class<?>, Status> patternFilter; 501 if (p.endsWith("*")) { 502 // Wildcard cases 503 if (p.endsWith(".*")) { 504 // Pattern is a package name with a wildcard 505 final String pkg = p.substring(poffset, nameLen - 2); 506 if (pkg.isEmpty()) { 507 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 508 } 509 if (negate) { 510 // A Function that fails if the class starts with the pattern, otherwise don't care 511 patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED; 512 } else { 513 // A Function that succeeds if the class starts with the pattern, otherwise don't care 514 patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED; 515 } 516 } else if (p.endsWith(".**")) { 517 // Pattern is a package prefix with a double wildcard 518 final String pkgs = p.substring(poffset, nameLen - 2); 519 if (pkgs.length() < 2) { 520 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 521 } 522 if (negate) { 523 // A Function that fails if the class starts with the pattern, otherwise don't care 524 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED; 525 } else { 526 // A Function that succeeds if the class starts with the pattern, otherwise don't care 527 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED; 528 } 529 } else { 530 // Pattern is a classname (possibly empty) with a trailing wildcard 531 final String className = p.substring(poffset, nameLen - 1); 532 if (negate) { 533 // A Function that fails if the class starts with the pattern, otherwise don't care 534 patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED; 535 } else { 536 // A Function that succeeds if the class starts with the pattern, otherwise don't care 537 patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED; 538 } 539 } 540 } else { 541 final String name = p.substring(poffset); 542 if (name.isEmpty()) { 543 throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\""); 544 } 545 // Pattern is a class name 546 if (negate) { 547 // A Function that fails if the class equals the pattern, otherwise don't care 548 patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED; 549 } else { 550 // A Function that succeeds if the class equals the pattern, otherwise don't care 551 patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED; 552 } 553 } 554 // If there is a moduleName, combine the module name check with the package/class check 555 if (moduleName == null) { 556 filters.add(patternFilter); 557 } else { 558 filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED); 559 } 560 } 561 if (filters.isEmpty() && !hasLimits) { 562 throw new UnsupportedOperationException("no non-empty patterns"); 563 } 564 } 565 566 /** 567 * Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences. 568 * 569 * @param pattern a string with a type name, '=' and a value 570 * @return {@code true} if a limit was parsed, else {@code false} 571 * @throws IllegalArgumentException if the pattern is missing 572 * the name, the Long value is not a number or is negative. 573 */ 574 private boolean parseLimit(String pattern) { 575 int eqNdx = pattern.indexOf('='); 576 if (eqNdx < 0) { 577 // not a limit pattern 578 return false; 579 } 580 String valueString = pattern.substring(eqNdx + 1); 581 if (pattern.startsWith("maxdepth=")) { 582 maxDepth = parseValue(valueString); 583 } else if (pattern.startsWith("maxarray=")) { 584 maxArrayLength = parseValue(valueString); 585 } else if (pattern.startsWith("maxrefs=")) { 586 maxReferences = parseValue(valueString); 587 } else if (pattern.startsWith("maxbytes=")) { 588 maxStreamBytes = parseValue(valueString); 589 } else { 590 throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx)); 591 } 592 return true; 593 } 594 595 /** 596 * Parse the value of a limit and check that it is non-negative. 597 * @param string inputstring 598 * @return the parsed value 599 * @throws IllegalArgumentException if parsing the value fails or the value is negative 600 */ 601 private static long parseValue(String string) throws IllegalArgumentException { 602 // Parse a Long from after the '=' to the end 603 long value = Long.parseLong(string); 604 if (value < 0) { 605 throw new IllegalArgumentException("negative limit: " + string); 606 } 607 return value; 608 } 609 610 /** 611 * {@inheritDoc} 612 */ 613 @Override 614 public Status checkInput(FilterInfo filterInfo) { 615 if (filterInfo.references() < 0 616 || filterInfo.depth() < 0 617 || filterInfo.streamBytes() < 0 618 || filterInfo.references() > maxReferences 619 || filterInfo.depth() > maxDepth 620 || filterInfo.streamBytes() > maxStreamBytes) { 621 return Status.REJECTED; 622 } 623 624 Class<?> clazz = filterInfo.serialClass(); 625 if (clazz != null) { 626 if (clazz.isArray()) { 627 if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) { 628 // array length is too big 629 return Status.REJECTED; 630 } 631 if (!checkComponentType) { 632 // As revised; do not check the component type for arrays 633 return Status.UNDECIDED; 634 } 635 do { 636 // Arrays are decided based on the component type 637 clazz = clazz.getComponentType(); 638 } while (clazz.isArray()); 639 } 640 641 if (clazz.isPrimitive()) { 642 // Primitive types are undecided; let someone else decide 643 return Status.UNDECIDED; 644 } else { 645 // Find any filter that allowed or rejected the class 646 final Class<?> cl = clazz; 647 Optional<Status> status = filters.stream() 648 .map(f -> f.apply(cl)) 649 .filter(p -> p != Status.UNDECIDED) 650 .findFirst(); 651 return status.orElse(Status.UNDECIDED); 652 } 653 } 654 return Status.UNDECIDED; 655 } 656 657 /** 658 * Returns {@code true} if the class is in the package. 659 * 660 * @param c a class 661 * @param pkg a package name 662 * @return {@code true} if the class is in the package, 663 * otherwise {@code false} 664 */ 665 private static boolean matchesPackage(Class<?> c, String pkg) { 666 return pkg.equals(c.getPackageName()); 667 } 668 669 /** 670 * Returns the pattern used to create this filter. 671 * @return the pattern used to create this filter 672 */ 673 @Override 674 public String toString() { 675 return pattern; 676 } 677 } 678 } 679 }