001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.reflect; 018 019import static java.lang.Character.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023 024import java.lang.reflect.*; 025import java.util.*; 026import java.util.function.*; 027import java.util.stream.*; 028 029import org.apache.juneau.commons.collections.*; 030import org.apache.juneau.commons.utils.*; 031 032/** 033 * Maps arbitrary values to classes, methods, fields, and constructors based on name-based patterns. 034 * 035 * <p> 036 * This utility allows you to create flexible mappings between reflection elements and custom values. 037 * It supports various pattern formats for identifying classes and their members, with intelligent 038 * matching that handles inner classes, method signatures, and field names. 039 * 040 * <h5 class='section'>Example:</h5> 041 * <p class='bjava'> 042 * <jc>// Create a map that associates visibility levels with specific classes/methods</jc> 043 * ReflectionMap<Visibility> <jv>map</jv> = ReflectionMap 044 * .<jsm>create</jsm>(Visibility.<jk>class</jk>) 045 * .append(<js>"com.foo.MyClass"</js>, Visibility.<jsf>PUBLIC</jsf>) 046 * .append(<js>"com.foo.MyClass.secretMethod"</js>, Visibility.<jsf>PRIVATE</jsf>) 047 * .append(<js>"com.foo.MyClass.internalField"</js>, Visibility.<jsf>PACKAGE</jsf>) 048 * .build(); 049 * 050 * <jc>// Find values for specific reflection elements</jc> 051 * Stream<Visibility> <jv>classVisibility</jv> = <jv>map</jv>.find(MyClass.<jk>class</jk>); 052 * Stream<Visibility> <jv>methodVisibility</jv> = <jv>map</jv>.find(<jv>secretMethod</jv>); 053 * </p> 054 * 055 * <h5 class='section'>Supported Pattern Formats:</h5> 056 * 057 * <p> 058 * The following pattern formats are supported for mapping keys: 059 * 060 * <ul class='spaced-list'> 061 * <li><b>Classes:</b> 062 * <ul> 063 * <li>Fully qualified: 064 * <ul> 065 * <li><js>"com.foo.MyClass"</js> 066 * </ul> 067 * <li>Fully qualified inner class: 068 * <ul> 069 * <li><js>"com.foo.MyClass$Inner1$Inner2"</js> 070 * </ul> 071 * <li>Simple class name: 072 * <ul> 073 * <li><js>"MyClass"</js> 074 * </ul> 075 * <li>Simple inner class: 076 * <ul> 077 * <li><js>"MyClass$Inner1$Inner2"</js> 078 * <li><js>"Inner1$Inner2"</js> 079 * <li><js>"Inner2"</js> 080 * </ul> 081 * <li>All classes wildcard: 082 * <ul> 083 * <li><js>"*"</js> 084 * </ul> 085 * </ul> 086 * <li><b>Methods:</b> 087 * <ul> 088 * <li>Fully qualified with args: 089 * <ul> 090 * <li><js>"com.foo.MyClass.myMethod(String,int)"</js> 091 * <li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js> 092 * <li><js>"com.foo.MyClass.myMethod()"</js> 093 * </ul> 094 * <li>Fully qualified without args (matches any args): 095 * <ul> 096 * <li><js>"com.foo.MyClass.myMethod"</js> 097 * </ul> 098 * <li>Simple with args: 099 * <ul> 100 * <li><js>"MyClass.myMethod(String,int)"</js> 101 * <li><js>"MyClass.myMethod(java.lang.String,int)"</js> 102 * <li><js>"MyClass.myMethod()"</js> 103 * </ul> 104 * <li>Simple without args (matches any args): 105 * <ul> 106 * <li><js>"MyClass.myMethod"</js> 107 * </ul> 108 * <li>Simple inner class: 109 * <ul> 110 * <li><js>"MyClass$Inner1$Inner2.myMethod"</js> 111 * <li><js>"Inner1$Inner2.myMethod"</js> 112 * <li><js>"Inner2.myMethod"</js> 113 * </ul> 114 * </ul> 115 * <li><b>Fields:</b> 116 * <ul> 117 * <li>Fully qualified: 118 * <ul> 119 * <li><js>"com.foo.MyClass.myField"</js> 120 * </ul> 121 * <li>Simple: 122 * <ul> 123 * <li><js>"MyClass.myField"</js> 124 * </ul> 125 * <li>Simple inner class: 126 * <ul> 127 * <li><js>"MyClass$Inner1$Inner2.myField"</js> 128 * <li><js>"Inner1$Inner2.myField"</js> 129 * <li><js>"Inner2.myField"</js> 130 * </ul> 131 * </ul> 132 * <li><b>Constructors:</b> 133 * <ul> 134 * <li>Fully qualified with args: 135 * <ul> 136 * <li><js>"com.foo.MyClass(String,int)"</js> 137 * <li><js>"com.foo.MyClass(java.lang.String,int)"</js> 138 * <li><js>"com.foo.MyClass()"</js> 139 * </ul> 140 * <li>Simple with args: 141 * <ul> 142 * <li><js>"MyClass(String,int)"</js> 143 * <li><js>"MyClass(java.lang.String,int)"</js> 144 * <li><js>"MyClass()"</js> 145 * </ul> 146 * <li>Simple inner class: 147 * <ul> 148 * <li><js>"MyClass$Inner1$Inner2()"</js> 149 * <li><js>"Inner1$Inner2()"</js> 150 * <li><js>"Inner2()"</js> 151 * </ul> 152 * </ul> 153 * <li><b>Multiple patterns:</b> 154 * <ul> 155 * <li>A comma-delimited list of any patterns above: 156 * <ul> 157 * <li><js>"com.foo.MyClass, com.bar.OtherClass"</js> 158 * <li><js>"MyClass.method1, MyClass.method2, MyClass.field1"</js> 159 * </ul> 160 * </ul> 161 * </ul> 162 * 163 * <h5 class='section'>Notes:</h5> 164 * <ul class='spaced-list'> 165 * <li>Method and constructor patterns without parentheses match any method signature. 166 * <li>Method and constructor argument types can be specified with simple or fully qualified names. 167 * <li>Array types in signatures use either <js>"Type[]"</js> or JVM notation like <js>"[LType;"</js>. 168 * <li>Generic type parameters are stripped from method signatures during matching. 169 * <li>Patterns are case-sensitive. 170 * </ul> 171 * 172 * <h5 class='section'>See Also:</h5> 173 * <ul class='seealso'> 174 * <li class='jc'>{@link ClassInfo} 175 * <li class='jc'>{@link MethodInfo} 176 * <li class='jc'>{@link FieldInfo} 177 * <li class='jc'>{@link ConstructorInfo} 178 * </ul> 179 * 180 * @param <V> The type of values stored in this map. 181 */ 182@SuppressWarnings({ 183 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 184}) 185public class ReflectionMap<V> { 186 187 /** 188 * Builder for creating {@link ReflectionMap} instances. 189 * 190 * <h5 class='section'>Example:</h5> 191 * <p class='bjava'> 192 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 193 * .<jsm>create</jsm>(String.<jk>class</jk>) 194 * .append(<js>"MyClass"</js>, <js>"value1"</js>) 195 * .append(<js>"MyClass.myMethod"</js>, <js>"value2"</js>) 196 * .build(); 197 * </p> 198 * 199 * @param <V> The type of values stored in this map. 200 */ 201 public static class Builder<V> { 202 final List<ClassEntry<V>> classEntries; 203 final List<MethodEntry<V>> methodEntries; 204 final List<FieldEntry<V>> fieldEntries; 205 final List<ConstructorEntry<V>> constructorEntries; 206 207 /** 208 * Constructor. 209 */ 210 protected Builder() { 211 classEntries = list(); 212 methodEntries = list(); 213 fieldEntries = list(); 214 constructorEntries = list(); 215 } 216 217 /** 218 * Adds one or more mappings to this builder. 219 * 220 * <p> 221 * This method accepts pattern strings that identify classes, methods, fields, or constructors, 222 * and associates them with the specified value. Multiple patterns can be specified in a single 223 * key using comma-delimited format. 224 * 225 * <h5 class='section'>Example:</h5> 226 * <p class='bjava'> 227 * Builder<String> <jv>builder</jv> = ReflectionMap.<jsm>create</jsm>(String.<jk>class</jk>); 228 * 229 * <jc>// Map a class</jc> 230 * <jv>builder</jv>.append(<js>"com.foo.MyClass"</js>, <js>"classValue"</js>); 231 * 232 * <jc>// Map a specific method</jc> 233 * <jv>builder</jv>.append(<js>"MyClass.myMethod(String,int)"</js>, <js>"methodValue"</js>); 234 * 235 * <jc>// Map multiple patterns at once</jc> 236 * <jv>builder</jv>.append(<js>"MyClass.field1, MyClass.field2, MyClass.field3"</js>, <js>"fieldValue"</js>); 237 * </p> 238 * 239 * @param key 240 * The mapping key pattern(s). Must not be <jk>null</jk>. 241 * <br>Can be any of the following: 242 * <ul> 243 * <li>Fully qualified class name (e.g., <js>"com.foo.MyClass"</js>) 244 * <li>Simple class name (e.g., <js>"MyClass"</js>) 245 * <li>All classes wildcard (e.g., <js>"*"</js>) 246 * <li>Method with signature (e.g., <js>"com.foo.MyClass.myMethod(String,int)"</js>) 247 * <li>Method without signature (e.g., <js>"MyClass.myMethod"</js>) - matches any signature 248 * <li>Field (e.g., <js>"MyClass.myField"</js>) 249 * <li>Constructor (e.g., <js>"MyClass(String,int)"</js>) 250 * <li>Comma-delimited list of any of the above (e.g., <js>"MyClass, MyClass.method1, MyClass.field1"</js>) 251 * </ul> 252 * @param value 253 * The value to associate with the matching reflection element(s). 254 * <br>Can be <jk>null</jk> (the key pattern will then map to a <jk>null</jk> value). 255 * @return This object. 256 * @throws RuntimeException If the key pattern is invalid or empty. 257 */ 258 public Builder<V> append(String key, V value) { 259 if (Shorts.ie(key)) // NOAI 260 throw rex("Invalid reflection signature: [%s]", key); 261 try { 262 splitNames(key, k -> { 263 if (k.endsWith(")")) { 264 int i = k.substring(0, k.indexOf('(')).lastIndexOf('.'); 265 if (i == -1 || isUpperCase(k.charAt(i + 1))) { 266 constructorEntries.add(ConstructorEntry.parse(k, value)); 267 } else { 268 methodEntries.add(MethodEntry.parse(k, value)); 269 } 270 } else { 271 var i = k.lastIndexOf('.'); 272 if (i == -1) { 273 classEntries.add(ClassEntry.parse(k, value)); 274 } else if (isUpperCase(k.charAt(i + 1))) { 275 classEntries.add(ClassEntry.parse(k, value)); 276 fieldEntries.add(FieldEntry.parse(k, value)); 277 } else { 278 methodEntries.add(MethodEntry.parse(k, value)); 279 fieldEntries.add(FieldEntry.parse(k, value)); 280 } 281 } 282 }); 283 } catch (@SuppressWarnings("unused") IndexOutOfBoundsException e) { 284 throw rex("Invalid reflection signature: [%s]", key); 285 } 286 287 return this; 288 } 289 290 /** 291 * Builds a new {@link ReflectionMap} from the current state of this builder. 292 * 293 * <p> 294 * Multiple calls to this method will create independent copies of the map. 295 * 296 * @return A new immutable {@link ReflectionMap} instance. 297 */ 298 public ReflectionMap<V> build() { 299 return new ReflectionMap<>(this); 300 } 301 302 /** 303 * Splits a comma-delimited key string into individual keys, respecting escaped commas within parentheses. 304 * 305 * @param key The key string to split. 306 * @param consumer The consumer to accept each split key. 307 */ 308 private static void splitNames(String key, Consumer<String> consumer) { 309 if (key.indexOf(',') == -1) { 310 consumer.accept(key); 311 } else { 312 var m = 0; 313 var escaped = false; 314 for (var i = 0; i < key.length(); i++) { 315 var c = key.charAt(i); 316 if (c == '(') 317 escaped = true; 318 else if (c == ')') 319 escaped = false; 320 else if (c == ',' && ! escaped) { 321 consumer.accept(key.substring(m, i).trim()); 322 m = i + 1; 323 } 324 } 325 consumer.accept(key.substring(m).trim()); 326 } 327 } 328 } 329 330 private record ClassEntry<V>(String simpleName, String fullName, V value) { 331 332 static <V> ClassEntry<V> parse(String name, V value) { 333 return new ClassEntry<>(simpleClassName(name), name, value); 334 } 335 336 public boolean matches(Class<?> c) { 337 if (c == null) 338 return false; 339 return classMatches(simpleName, fullName, c); 340 } 341 342 protected FluentMap<String,Object> properties() { 343 // @formatter:off 344 return filteredBeanPropertyMap() 345 .a("fullName", fullName) 346 .a("simpleName", simpleName) 347 .a("value", value); 348 // @formatter:on 349 } 350 351 @Override 352 public String toString() { 353 return r(properties()); 354 } 355 } 356 357 record ConstructorEntry<V>(String simpleClassName, String fullClassName, String[] args, V value) { 358 359 static <V> ConstructorEntry<V> parse(String name, V value) { 360 var i = name.indexOf('('); 361 var args = splitMethodArgs(name.substring(i + 1, name.length() - 1)); 362 for (var j = 0; j < args.length; j++) { 363 // Strip off generic parameters (e.g., "List<String>[]" -> "List[]") 364 args[j] = stripGenerics(args[j]); 365 } 366 var cn = name.substring(0, i).trim(); 367 return new ConstructorEntry<>(ReflectionMap.simpleClassName(cn), cn, args, value); 368 } 369 370 public boolean matches(Constructor<?> m) { 371 if (m == null) 372 return false; 373 var c = m.getDeclaringClass(); 374 return classMatches(simpleClassName, fullClassName, c) && (argsMatch(args, m.getParameterTypes())); 375 } 376 377 @Override /* Content-based: the 'args' array must compare by content, not identity (S6218). */ 378 public boolean equals(Object o) { 379 return o instanceof ConstructorEntry<?> e 380 && eq(simpleClassName, e.simpleClassName) 381 && eq(fullClassName, e.fullClassName) 382 && Arrays.equals(args, e.args) 383 && eq(value, e.value); 384 } 385 386 @Override 387 public int hashCode() { 388 return 31 * Objects.hash(simpleClassName, fullClassName, value) + Arrays.hashCode(args); 389 } 390 391 protected FluentMap<String,Object> properties() { 392 // @formatter:off 393 return filteredBeanPropertyMap() 394 .a("args", args) 395 .a("fullClassName", fullClassName) 396 .a("simpleClassName", simpleClassName) 397 .a("value", value); 398 // @formatter:on 399 } 400 401 @Override 402 public String toString() { 403 return r(properties()); 404 } 405 } 406 407 private record FieldEntry<V>(String simpleClassName, String fullClassName, String fieldName, V value) { 408 409 static <V> FieldEntry<V> parse(String name, V value) { 410 var i = name.lastIndexOf('.'); 411 var s1 = name.substring(0, i); 412 var s2 = name.substring(i + 1); 413 return new FieldEntry<>(ReflectionMap.simpleClassName(s1), s1, s2, value); 414 } 415 416 public boolean matches(Field f) { 417 if (f == null) 418 return false; 419 var c = f.getDeclaringClass(); 420 return classMatches(simpleClassName, fullClassName, c) && (eq(f.getName(), fieldName)); 421 } 422 423 protected FluentMap<String,Object> properties() { 424 // @formatter:off 425 return filteredBeanPropertyMap() 426 .a("fieldName", fieldName) 427 .a("fullClassName", fullClassName) 428 .a("simpleClassName", simpleClassName) 429 .a("value", value); 430 // @formatter:on 431 } 432 433 @Override 434 public String toString() { 435 return r(properties()); 436 } 437 } 438 439 record MethodEntry<V>(String simpleClassName, String fullClassName, String methodName, String[] args, V value) { 440 441 static <V> MethodEntry<V> parse(String name, V value) { 442 var i = name.indexOf('('); 443 var args = i == -1 ? null : splitMethodArgs(name.substring(i + 1, name.length() - 1)); 444 if (nn(args)) { 445 for (var j = 0; j < args.length; j++) { 446 // Strip off generic parameters (e.g., "List<String>[]" -> "List[]") 447 args[j] = stripGenerics(args[j]); 448 } 449 } 450 var n = i == -1 ? name : name.substring(0, i); 451 i = n.lastIndexOf('.'); 452 var s1 = n.substring(0, i).trim(); 453 var s2 = n.substring(i + 1).trim(); 454 return new MethodEntry<>(ReflectionMap.simpleClassName(s1), s1, s2, args, value); 455 } 456 457 public boolean matches(Method m) { 458 if (m == null) 459 return false; 460 var c = m.getDeclaringClass(); 461 // @formatter:off 462 return 463 classMatches(simpleClassName, fullClassName, c) 464 && (eq(m.getName(), methodName)) 465 && (argsMatch(args, m.getParameterTypes())); 466 // @formatter:on 467 } 468 469 @Override /* Content-based: the 'args' array must compare by content, not identity (S6218). */ 470 public boolean equals(Object o) { 471 return o instanceof MethodEntry<?> e 472 && eq(simpleClassName, e.simpleClassName) 473 && eq(fullClassName, e.fullClassName) 474 && eq(methodName, e.methodName) 475 && Arrays.equals(args, e.args) 476 && eq(value, e.value); 477 } 478 479 @Override 480 public int hashCode() { 481 return 31 * Objects.hash(simpleClassName, fullClassName, methodName, value) + Arrays.hashCode(args); 482 } 483 484 protected FluentMap<String,Object> properties() { 485 // @formatter:off 486 return filteredBeanPropertyMap() 487 .a("args", o(args).map(x -> '[' + toCdl(x) + "]").orElse(null)) 488 .a("fullClassName", fullClassName) 489 .a("methodName", methodName) 490 .a("simpleClassName", simpleClassName) 491 .a("value", value); 492 // @formatter:on 493 } 494 495 @Override 496 public String toString() { 497 return r(properties()); 498 } 499 } 500 501 /** 502 * Creates a new builder for constructing a {@link ReflectionMap}. 503 * 504 * <h5 class='section'>Example:</h5> 505 * <p class='bjava'> 506 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 507 * .<jsm>create</jsm>(String.<jk>class</jk>) 508 * .append(<js>"com.foo.MyClass"</js>, <js>"value1"</js>) 509 * .append(<js>"com.foo.MyClass.myMethod"</js>, <js>"value2"</js>) 510 * .build(); 511 * </p> 512 * 513 * @param <V> The type of values stored in the map. 514 * @param c The class type of values (used for type safety, not stored). Can be <jk>null</jk>. 515 * @return A new builder instance. 516 */ 517 @SuppressWarnings({ 518 "java:S1172" // Parameter c is used for type inference, not runtime behavior 519 }) 520 public static <V> Builder<V> create(Class<V> c) { 521 return new Builder<>(); 522 } 523 524 private static boolean argMatches(String pattern, Class<?> type) { 525 // Extract base type and dimensions from pattern 526 var patternDims = 0; 527 var patternBase = pattern; 528 while (patternBase.endsWith("[]")) { 529 patternDims++; 530 patternBase = patternBase.substring(0, patternBase.length() - 2); 531 } 532 533 // Extract base type and dimensions from actual type 534 var typeDims = 0; 535 var typeBase = type; 536 while (typeBase.isArray()) { 537 typeDims++; 538 typeBase = typeBase.getComponentType(); 539 } 540 541 // Array dimensions must match 542 if (patternDims != typeDims) 543 return false; 544 545 // If non-array, use simple comparison 546 if (patternDims == 0) 547 return eq(pattern, type.getSimpleName()) || eq(pattern, type.getName()); 548 549 // For arrays, compare the component types (simple name or full name) 550 return eq(patternBase, typeBase.getSimpleName()) || eq(patternBase, typeBase.getName()); 551 } 552 553 private static boolean argsMatch(String[] names, Class<?>[] args) { 554 if (names == null) 555 return true; 556 if (names.length != args.length) 557 return false; 558 for (var i = 0; i < args.length; i++) { 559 if (! argMatches(names[i], args[i])) 560 return false; 561 } 562 return true; 563 } 564 565 private static boolean classMatches(String simpleName, String fullName, Class<?> c) { 566 // For class org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder 567 // c.getSimpleName() == "Builder" 568 // c.getFullName() == "org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder" 569 // c.getPackage() == "org.apache.juneau.a.rttests" 570 var cSimple = cns(c); 571 var cFull = cn(c); 572 if (eq(simpleName, cSimple) || eq(fullName, cFull) || eq(simpleName, "*")) 573 return true; 574 if (cFull.indexOf('$') != -1) { 575 var p = c.getPackage(); 576 if (nn(p)) 577 cFull = cFull.substring(p.getName().length() + 1); 578 if (eq(simpleName, cFull)) 579 return true; 580 var i = cFull.indexOf('$'); 581 while (i != -1) { 582 cFull = cFull.substring(i + 1); 583 if (eq(simpleName, cFull)) 584 return true; 585 i = cFull.indexOf('$'); 586 } 587 } 588 return false; 589 } 590 591 private static String simpleClassName(String name) { 592 var i = name.indexOf('.'); 593 if (i == -1) 594 return name; 595 // Return null for fully qualified names to ensure they only match by fullName, not by simpleName. 596 // This prevents ambiguous matches where a simple name pattern might accidentally match 597 // a fully qualified pattern's simple part. 598 return null; 599 } 600 601 602 private static String stripGenerics(String type) { 603 if (type.indexOf('<') == -1) 604 return type; 605 var sb = new StringBuilder(type.length()); 606 var depth = 0; 607 for (var i = 0; i < type.length(); i++) { 608 var c = type.charAt(i); 609 if (c == '<') { 610 depth++; 611 } else if (c == '>') { 612 depth--; 613 } else if (depth == 0) { 614 sb.append(c); 615 } 616 } 617 return sb.toString(); 618 } 619 620 final List<ClassEntry<V>> classEntries; 621 final List<MethodEntry<V>> methodEntries; 622 final List<FieldEntry<V>> fieldEntries; 623 final List<ConstructorEntry<V>> constructorEntries; 624 625 /** 626 * Constructor. 627 * 628 * @param b The builder containing the mappings to initialize this map with. Must not be <jk>null</jk>. 629 */ 630 protected ReflectionMap(Builder<V> b) { 631 this.classEntries = u(cp(b.classEntries)); 632 this.methodEntries = u(cp(b.methodEntries)); 633 this.fieldEntries = u(cp(b.fieldEntries)); 634 this.constructorEntries = u(cp(b.constructorEntries)); 635 } 636 637 /** 638 * Finds all values associated with the specified class. 639 * 640 * <p> 641 * This method searches for mappings that match the given class, including patterns for 642 * the fully qualified name, simple name, inner class names, and wildcard patterns. 643 * 644 * <h5 class='section'>Example:</h5> 645 * <p class='bjava'> 646 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 647 * .<jsm>create</jsm>(String.<jk>class</jk>) 648 * .append(<js>"com.foo.MyClass"</js>, <js>"value1"</js>) 649 * .append(<js>"MyClass"</js>, <js>"value2"</js>) 650 * .build(); 651 * 652 * <jc>// Find all values for MyClass</jc> 653 * Stream<String> <jv>values</jv> = <jv>map</jv>.find(MyClass.<jk>class</jk>); 654 * <jc>// Returns stream containing ["value1", "value2"]</jc> 655 * </p> 656 * 657 * @param c The class to find mappings for. Can be <jk>null</jk>. 658 * @return A stream of all values associated with the class. Empty stream if no matches found. 659 */ 660 public Stream<V> find(Class<?> c) { 661 return classEntries.stream().filter(x -> x.matches(c)).map(x -> x.value()); 662 } 663 664 /** 665 * Finds all values associated with the specified constructor. 666 * 667 * <p> 668 * This method searches for mappings that match the given constructor, considering both 669 * the declaring class and parameter types. 670 * 671 * <h5 class='section'>Example:</h5> 672 * <p class='bjava'> 673 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 674 * .<jsm>create</jsm>(String.<jk>class</jk>) 675 * .append(<js>"MyClass(String,int)"</js>, <js>"value1"</js>) 676 * .build(); 677 * 678 * <jc>// Find value for specific constructor</jc> 679 * Constructor<?> <jv>ctor</jv> = MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>, <jk>int</jk>.<jk>class</jk>); 680 * Stream<String> <jv>values</jv> = <jv>map</jv>.find(<jv>ctor</jv>); 681 * </p> 682 * 683 * @param c The constructor to find mappings for. Can be <jk>null</jk>. 684 * @return A stream of all values associated with the constructor. Empty stream if no matches found. 685 */ 686 public Stream<V> find(Constructor<?> c) { 687 return constructorEntries.stream().filter(x -> x.matches(c)).map(x -> x.value()); 688 } 689 690 /** 691 * Finds all values associated with the specified field. 692 * 693 * <p> 694 * This method searches for mappings that match the given field, considering both 695 * the declaring class and field name. 696 * 697 * <h5 class='section'>Example:</h5> 698 * <p class='bjava'> 699 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 700 * .<jsm>create</jsm>(String.<jk>class</jk>) 701 * .append(<js>"MyClass.myField"</js>, <js>"value1"</js>) 702 * .build(); 703 * 704 * <jc>// Find value for specific field</jc> 705 * Field <jv>field</jv> = MyClass.<jk>class</jk>.getField(<js>"myField"</js>); 706 * Stream<String> <jv>values</jv> = <jv>map</jv>.find(<jv>field</jv>); 707 * </p> 708 * 709 * @param f The field to find mappings for. Can be <jk>null</jk>. 710 * @return A stream of all values associated with the field. Empty stream if no matches found. 711 */ 712 public Stream<V> find(Field f) { 713 return fieldEntries.stream().filter(x -> x.matches(f)).map(x -> x.value()); 714 } 715 716 /** 717 * Finds all values associated with the specified method. 718 * 719 * <p> 720 * This method searches for mappings that match the given method, considering the 721 * declaring class, method name, and parameter types. 722 * 723 * <h5 class='section'>Example:</h5> 724 * <p class='bjava'> 725 * ReflectionMap<String> <jv>map</jv> = ReflectionMap 726 * .<jsm>create</jsm>(String.<jk>class</jk>) 727 * .append(<js>"MyClass.myMethod"</js>, <js>"value1"</js>) 728 * .append(<js>"MyClass.myMethod(String)"</js>, <js>"value2"</js>) 729 * .build(); 730 * 731 * <jc>// Find values for specific method</jc> 732 * Method <jv>method</jv> = MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>, String.<jk>class</jk>); 733 * Stream<String> <jv>values</jv> = <jv>map</jv>.find(<jv>method</jv>); 734 * <jc>// Returns stream containing ["value1", "value2"] - first matches any signature</jc> 735 * </p> 736 * 737 * @param m The method to find mappings for. Can be <jk>null</jk>. 738 * @return A stream of all values associated with the method. Empty stream if no matches found. 739 */ 740 public Stream<V> find(Method m) { 741 return methodEntries.stream().filter(x -> x.matches(m)).map(x -> x.value()); 742 } 743 744 protected FluentMap<String,Object> properties() { 745 // @formatter:off 746 return filteredBeanPropertyMap() 747 .a("classEntries", classEntries) 748 .a("methodEntries", methodEntries) 749 .a("fieldEntries", fieldEntries) 750 .a("constructorEntries", constructorEntries); 751 // @formatter:on 752 } 753 754 @Override /* Overridden from Object */ 755 public String toString() { 756 return r(properties()); 757 } 758}