001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.utils; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static java.lang.Character.*; 017 018import java.lang.reflect.*; 019import java.util.*; 020 021import org.apache.juneau.collections.*; 022 023/** 024 * Allows arbitrary objects to be mapped to classes and methods base on class/method name keys. 025 * 026 * <p> 027 * The valid pattern matches are: 028 * <ul class='spaced-list'> 029 * <li>Classes: 030 * <ul> 031 * <li>Fully qualified: 032 * <ul> 033 * <li><js>"com.foo.MyClass"</js> 034 * </ul> 035 * <li>Fully qualified inner class: 036 * <ul> 037 * <li><js>"com.foo.MyClass$Inner1$Inner2"</js> 038 * </ul> 039 * <li>Simple: 040 * <ul> 041 * <li><js>"MyClass"</js> 042 * </ul> 043 * <li>Simple inner: 044 * <ul> 045 * <li><js>"MyClass$Inner1$Inner2"</js> 046 * <li><js>"Inner1$Inner2"</js> 047 * <li><js>"Inner2"</js> 048 * </ul> 049 * </ul> 050 * <li>Methods: 051 * <ul> 052 * <li>Fully qualified with args: 053 * <ul> 054 * <li><js>"com.foo.MyClass.myMethod(String,int)"</js> 055 * <li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js> 056 * <li><js>"com.foo.MyClass.myMethod()"</js> 057 * </ul> 058 * <li>Fully qualified: 059 * <ul> 060 * <li><js>"com.foo.MyClass.myMethod"</js> 061 * </ul> 062 * <li>Simple with args: 063 * <ul> 064 * <li><js>"MyClass.myMethod(String,int)"</js> 065 * <li><js>"MyClass.myMethod(java.lang.String,int)"</js> 066 * <li><js>"MyClass.myMethod()"</js> 067 * </ul> 068 * <li>Simple: 069 * <ul> 070 * <li><js>"MyClass.myMethod"</js> 071 * </ul> 072 * <li>Simple inner class: 073 * <ul> 074 * <li><js>"MyClass$Inner1$Inner2.myMethod"</js> 075 * <li><js>"Inner1$Inner2.myMethod"</js> 076 * <li><js>"Inner2.myMethod"</js> 077 * </ul> 078 * </ul> 079 * <li>Fields: 080 * <ul> 081 * <li>Fully qualified: 082 * <ul> 083 * <li><js>"com.foo.MyClass.myField"</js> 084 * </ul> 085 * <li>Simple: 086 * <ul> 087 * <li><js>"MyClass.myField"</js> 088 * </ul> 089 * <li>Simple inner class: 090 * <ul> 091 * <li><js>"MyClass$Inner1$Inner2.myField"</js> 092 * <li><js>"Inner1$Inner2.myField"</js> 093 * <li><js>"Inner2.myField"</js> 094 * </ul> 095 * </ul> 096 * <li>Constructors: 097 * <ul> 098 * <li>Fully qualified with args: 099 * <ul> 100 * <li><js>"com.foo.MyClass(String,int)"</js> 101 * <li><js>"com.foo.MyClass(java.lang.String,int)"</js> 102 * <li><js>"com.foo.MyClass()"</js> 103 * </ul> 104 * <li>Simple with args: 105 * <ul> 106 * <li><js>"MyClass(String,int)"</js> 107 * <li><js>"MyClass(java.lang.String,int)"</js> 108 * <li><js>"MyClass()"</js> 109 * </ul> 110 * <li>Simple inner class: 111 * <ul> 112 * <li><js>"MyClass$Inner1$Inner2()"</js> 113 * <li><js>"Inner1$Inner2()"</js> 114 * <li><js>"Inner2()"</js> 115 * </ul> 116 * </ul> 117 * <li>A comma-delimited list of anything on this list. 118 * </ul> 119 * 120 * @param <V> The type of object in this map. 121 */ 122public class ReflectionMap<V> { 123 124 private final List<ClassEntry<V>> classEntries; 125 private final List<MethodEntry<V>> methodEntries; 126 private final List<FieldEntry<V>> fieldEntries; 127 private final List<ConstructorEntry<V>> constructorEntries; 128 final boolean noClassEntries, noMethodEntries, noFieldEntries, noConstructorEntries; 129 130 /** 131 * Constructor. 132 * 133 * @param b Initializer object. 134 */ 135 ReflectionMap(Builder<V> b) { 136 this.classEntries = Collections.unmodifiableList(new ArrayList<>(b.classEntries)); 137 this.methodEntries = Collections.unmodifiableList(new ArrayList<>(b.methodEntries)); 138 this.fieldEntries = Collections.unmodifiableList(new ArrayList<>(b.fieldEntries)); 139 this.constructorEntries = Collections.unmodifiableList(new ArrayList<>(b.constructorEntries)); 140 this.noClassEntries = classEntries.isEmpty(); 141 this.noMethodEntries = methodEntries.isEmpty(); 142 this.noFieldEntries = fieldEntries.isEmpty(); 143 this.noConstructorEntries = constructorEntries.isEmpty(); 144 } 145 146 /** 147 * Static builder creator. 148 * 149 * @param <V> The type of object in this map. 150 * @param c The type of object in this map. 151 * @return A new instance of this object. 152 */ 153 public static <V> ReflectionMap.Builder<V> create(Class<V> c) { 154 return new ReflectionMap.Builder<>(); 155 } 156 157 /** 158 * Creates a new builder object for {@link ReflectionMap} objects. 159 * 160 * @param <V> The type of object in this map. 161 */ 162 public static class Builder<V> { 163 List<ClassEntry<V>> classEntries = new ArrayList<>(); 164 List<MethodEntry<V>> methodEntries = new ArrayList<>(); 165 List<FieldEntry<V>> fieldEntries = new ArrayList<>(); 166 List<ConstructorEntry<V>> constructorEntries = new ArrayList<>(); 167 168 /** 169 * Adds a mapping to this builder. 170 * 171 * @param key 172 * The mapping key. 173 * <br>Can be any of the following: 174 * <ul> 175 * <li>Full class name (e.g. <js>"com.foo.MyClass"</js>). 176 * <li>Simple class name (e.g. <js>"MyClass"</js>). 177 * <li>All classes (e.g. <js>"*"</js>). 178 * <li>Full method name (e.g. <js>"com.foo.MyClass.myMethod"</js>). 179 * <li>Simple method name (e.g. <js>"MyClass.myMethod"</js>). 180 * <li>A comma-delimited list of anything on this list. 181 * </ul> 182 * @param value The value for this mapping. 183 * @return This object (for method chaining). 184 */ 185 public Builder<V> append(String key, V value) { 186 if (isEmpty(key)) 187 throw new RuntimeException("Invalid reflection signature: [" + key + "]"); 188 try { 189 for (String k : splitNames(key)) { 190 if (k.endsWith(")")) { 191 int i = k.substring(0, k.indexOf('(')).lastIndexOf('.'); 192 if (i == -1 || isUpperCase(k.charAt(i+1))) { 193 constructorEntries.add(new ConstructorEntry<>(k, value)); 194 } else { 195 methodEntries.add(new MethodEntry<>(k, value)); 196 } 197 } else { 198 int i = k.lastIndexOf('.'); 199 if (i == -1) { 200 classEntries.add(new ClassEntry<>(k, value)); 201 } else if (isUpperCase(k.charAt(i+1))) { 202 classEntries.add(new ClassEntry<>(k, value)); 203 fieldEntries.add(new FieldEntry<>(k, value)); 204 } else { 205 methodEntries.add(new MethodEntry<>(k, value)); 206 fieldEntries.add(new FieldEntry<>(k, value)); 207 } 208 } 209 } 210 } catch (IndexOutOfBoundsException e) { 211 throw new RuntimeException("Invalid reflection signature: [" + key + "]"); 212 } 213 214 return this; 215 } 216 217 /** 218 * Create new instance of {@link ReflectionMap} based on the contents of this builder. 219 * 220 * @return A new {@link ReflectionMap} object. 221 */ 222 public ReflectionMap<V> build() { 223 return new ReflectionMap<>(this); 224 } 225 } 226 227 static List<String> splitNames(String key) { 228 if (key.indexOf(',') == -1) 229 return Collections.singletonList(key.trim()); 230 List<String> l = new ArrayList<>(); 231 232 int m = 0; 233 boolean escaped = false; 234 for (int i = 0; i < key.length(); i++) { 235 char c = key.charAt(i); 236 if (c == '(') 237 escaped = true; 238 else if (c == ')') 239 escaped = false; 240 else if (c == ',' && ! escaped) { 241 l.add(key.substring(m, i).trim()); 242 m = i+1; 243 } 244 } 245 l.add(key.substring(m).trim()); 246 247 return l; 248 } 249 250 /** 251 * Finds first value in this map that matches the specified class. 252 * 253 * @param c The class to test for. 254 * @param ofType Only return objects of the specified type. 255 * @return The matching object. Never <jk>null</jk>. 256 */ 257 public Optional<V> find(Class<?> c, Class<? extends V> ofType) { 258 if (! noClassEntries) 259 for (ClassEntry<V> e : classEntries) 260 if (e.matches(c)) 261 if (ofType == null || ofType.isInstance(e.value)) 262 return Optional.ofNullable(e.value); 263 return Optional.empty(); 264 } 265 266 /** 267 * Finds first value in this map that matches the specified class. 268 * 269 * @param c The class to test for. 270 * @return The matching object. Never <jk>null</jk>. 271 */ 272 public Optional<V> find(Class<?> c) { 273 return find(c, null); 274 } 275 276 /** 277 * Finds all values in this map that matches the specified class. 278 * 279 * @param c The class to test for. 280 * @param ofType Only return objects of the specified type. 281 * @return A modifiable list of matching values. Never <jk>null</jk>. 282 */ 283 public List<V> findAll(Class<?> c, Class<? extends V> ofType) { 284 return appendAll(c, ofType, null); 285 } 286 287 /** 288 * Finds all values in this map that matches the specified class. 289 * 290 * @param c The class to test for. 291 * @return A modifiable list of matching values. Never <jk>null</jk>. 292 */ 293 public List<V> findAll(Class<?> c) { 294 return appendAll(c, null, null); 295 } 296 297 /** 298 * Finds all values in this map that matches the specified class. 299 * 300 * @param c The class to test for. 301 * @param ofType Only return objects of the specified type. 302 * @param l The list to append values to. Can be <jk>null</jk>. 303 * @return The same list passed in or a new modifiable list if <jk>null</jk>. 304 */ 305 public List<V> appendAll(Class<?> c, Class<? extends V> ofType, List<V> l) { 306 if (l == null) 307 l = AList.of(); 308 if (! noClassEntries) 309 for (ClassEntry<V> e : classEntries) 310 if (e.matches(c) && e.value != null) 311 if (ofType == null || ofType.isInstance(e.value)) 312 l.add(e.value); 313 return l; 314 } 315 316 /** 317 * Finds first value in this map that matches the specified method. 318 * 319 * @param m The method to test for. 320 * @param ofType Only return objects of the specified type. 321 * @return The matching object. Never <jk>null</jk>. 322 */ 323 public Optional<V> find(Method m, Class<? extends V> ofType) { 324 if (! noMethodEntries) 325 for (MethodEntry<V> e : methodEntries) 326 if (e.matches(m)) 327 if (ofType == null || ofType.isInstance(e.value)) 328 return Optional.ofNullable(e.value); 329 return Optional.empty(); 330 } 331 332 /** 333 * Finds first value in this map that matches the specified method. 334 * 335 * @param m The method to test for. 336 * @return The matching object. Never <jk>null</jk>. 337 */ 338 public Optional<V> find(Method m) { 339 return find(m, null); 340 } 341 342 /** 343 * Finds all values in this map that matches the specified method. 344 * 345 * @param m The method to test for. 346 * @param ofType Only return objects of the specified type. 347 * @return A modifiable list of matching values. Never <jk>null</jk>. 348 */ 349 public List<V> findAll(Method m, Class<? extends V> ofType) { 350 return appendAll(m, ofType, null); 351 } 352 353 /** 354 * Finds all values in this map that matches the specified method. 355 * 356 * @param m The method to test for. 357 * @return A modifiable list of matching values. Never <jk>null</jk>. 358 */ 359 public List<V> findAll(Method m) { 360 return appendAll(m, null, null); 361 } 362 363 /** 364 * Finds all values in this map that matches the specified method. 365 * 366 * @param m The method to test for. 367 * @param ofType Only return objects of the specified type. 368 * @param l The list to append values to. Can be <jk>null</jk>. 369 * @return The same list passed in or a new modifiable list if <jk>null</jk>. 370 */ 371 public List<V> appendAll(Method m, Class<? extends V> ofType, List<V> l) { 372 if (l == null) 373 l = AList.of(); 374 if (! noMethodEntries) 375 for (MethodEntry<V> e : methodEntries) 376 if (e.matches(m) && e.value != null) 377 if (ofType == null || ofType.isInstance(e.value)) 378 l.add(e.value); 379 return l; 380 } 381 382 /** 383 * Finds first value in this map that matches the specified field. 384 * 385 * @param f The field to test for. 386 * @param ofType Only return objects of the specified type. 387 * @return The matching object. Never <jk>null</jk>. 388 */ 389 public Optional<V> find(Field f, Class<? extends V> ofType) { 390 if (! noFieldEntries) 391 for (FieldEntry<V> e : fieldEntries) 392 if (e.matches(f)) 393 if (ofType == null || ofType.isInstance(e.value)) 394 return Optional.ofNullable(e.value); 395 return Optional.empty(); 396 } 397 398 /** 399 * Finds first value in this map that matches the specified field. 400 * 401 * @param f The field to test for. 402 * @return The matching object. Never <jk>null</jk>. 403 */ 404 public Optional<V> find(Field f) { 405 return find(f, null); 406 } 407 408 /** 409 * Finds all values in this map that matches the specified field. 410 * 411 * @param f The field to test for. 412 * @param ofType Only return objects of the specified type. 413 * @return A modifiable list of matching values. Never <jk>null</jk>. 414 */ 415 public List<V> findAll(Field f, Class<? extends V> ofType) { 416 return appendAll(f, ofType, null); 417 } 418 419 /** 420 * Finds all values in this map that matches the specified field. 421 * 422 * @param f The field to test for. 423 * @return A modifiable list of matching values. Never <jk>null</jk>. 424 */ 425 public List<V> findAll(Field f) { 426 return appendAll(f, null, null); 427 } 428 429 /** 430 * Finds all values in this map that matches the specified field. 431 * 432 * @param f The field to test for. 433 * @param ofType Only return objects of the specified type. 434 * @param l The list to append values to. Can be <jk>null</jk>. 435 * @return The same list passed in or a new modifiable list if <jk>null</jk>. 436 */ 437 public List<V> appendAll(Field f, Class<? extends V> ofType, List<V> l) { 438 if (l == null) 439 l = AList.of(); 440 if (! noFieldEntries) 441 for (FieldEntry<V> e : fieldEntries) 442 if (e.matches(f) && e.value != null) 443 if (ofType == null || ofType.isInstance(e.value)) 444 l.add(e.value); 445 return l == null ? new ArrayList<>(0) : l; 446 } 447 448 /** 449 * Finds first value in this map that matches the specified constructor. 450 * 451 * @param c The constructor to test for. 452 * @param ofType Only return objects of the specified type. 453 * @return The matching object. Never <jk>null</jk>. 454 */ 455 public Optional<V> find(Constructor<?> c, Class<? extends V> ofType) { 456 if (! noConstructorEntries) 457 for (ConstructorEntry<V> e : constructorEntries) 458 if (e.matches(c)) 459 if (ofType == null || ofType.isInstance(e.value)) 460 return Optional.ofNullable(e.value); 461 return Optional.empty(); 462 } 463 464 /** 465 * Finds first value in this map that matches the specified constructor. 466 * 467 * @param c The constructor to test for. 468 * @return The matching object. Never <jk>null</jk>. 469 */ 470 public Optional<V> find(Constructor<?> c) { 471 return find(c, null); 472 } 473 474 /** 475 * Finds all values in this map that matches the specified constructor. 476 * 477 * @param c The constructor to test for. 478 * @param ofType Only return objects of the specified type. 479 * @return A modifiable list of matching values. Never <jk>null</jk>. 480 */ 481 public List<V> findAll(Constructor<?> c, Class<? extends V> ofType) { 482 return appendAll(c, ofType, null); 483 } 484 485 /** 486 * Finds all values in this map that matches the specified constructor. 487 * 488 * @param c The constructor to test for. 489 * @return A modifiable list of matching values. Never <jk>null</jk>. 490 */ 491 public List<V> findAll(Constructor<?> c) { 492 return appendAll(c, null, null); 493 } 494 495 /** 496 * Finds all values in this map that matches the specified constructor. 497 * 498 * @param c The constructor to test for. 499 * @param ofType Only return objects of the specified type. 500 * @param l The list to append values to. Can be <jk>null</jk>. 501 * @return The same list passed in or a new modifiable list if <jk>null</jk>. 502 */ 503 public List<V> appendAll(Constructor<?> c, Class<? extends V> ofType, List<V> l) { 504 if (l == null) 505 l = AList.of(); 506 if (! noConstructorEntries) 507 for (ConstructorEntry<V> e : constructorEntries) 508 if (e.matches(c) && e.value != null) 509 if (ofType == null || ofType.isInstance(e.value)) 510 l.add(e.value); 511 return l; 512 } 513 514 static class ClassEntry<V> { 515 final String simpleName, fullName; 516 final V value; 517 518 ClassEntry(String name, V value) { 519 this.simpleName = simpleClassName(name); 520 this.fullName = name; 521 this.value = value; 522 } 523 524 public boolean matches(Class<?> c) { 525 if (c == null) 526 return false; 527 return classMatches(simpleName, fullName, c); 528 } 529 530 public OMap asMap() { 531 return OMap.of( 532 "simpleName", simpleName, 533 "fullName", fullName, 534 "value", value 535 ); 536 } 537 538 @Override 539 public String toString() { 540 return asMap().toString(); 541 } 542 } 543 544 static class MethodEntry<V> { 545 String simpleClassName, fullClassName, methodName, args[]; 546 V value; 547 548 MethodEntry(String name, V value) { 549 int i = name.indexOf('('); 550 this.args = i == -1 ? null : splitMethodArgs(name.substring(i+1, name.length()-1)); 551 if (args != null) { 552 for (int j = 0; j < args.length; j++) { 553 554 // Strip off generic parameters. 555 int k = args[j].indexOf('<'); 556 if (k > 0) 557 args[j] = args[j].substring(0, k); 558 559 // Convert from xxx[][] to [[Lxxx; notation. 560 if (args[j].endsWith("[]")) { 561 int l = 0; 562 while (args[j].endsWith("[]")) { 563 l++; 564 args[j] = args[j].substring(0, args[j].length()-2); 565 } 566 StringBuilder sb = new StringBuilder(args[j].length() + l + 2); 567 for (int m = 0; m < l; m++) 568 sb.append('['); 569 sb.append('L').append(args[j]).append(';'); 570 args[j] = sb.toString(); 571 } 572 } 573 } 574 name = i == -1 ? name : name.substring(0, i); 575 i = name.lastIndexOf('.'); 576 String s1 = name.substring(0, i).trim(), s2 = name.substring(i+1).trim(); 577 this.simpleClassName = simpleClassName(s1); 578 this.fullClassName = s1; 579 this.methodName = s2; 580 this.value = value; 581 } 582 583 public boolean matches(Method m) { 584 if (m == null) 585 return false; 586 Class<?> c = m.getDeclaringClass(); 587 return 588 classMatches(simpleClassName, fullClassName, c) 589 && (isEquals(m.getName(), methodName)) 590 && (argsMatch(args, m.getParameterTypes())); 591 } 592 593 public OMap asMap() { 594 return OMap.of( 595 "simpleClassName", simpleClassName, 596 "fullClassName", fullClassName, 597 "methodName", methodName, 598 "args", args, 599 "value", value 600 ); 601 } 602 603 @Override 604 public String toString() { 605 return asMap().toString(); 606 } 607 } 608 609 static class ConstructorEntry<V> { 610 String simpleClassName, fullClassName, args[]; 611 V value; 612 613 ConstructorEntry(String name, V value) { 614 int i = name.indexOf('('); 615 this.args = split(name.substring(i+1, name.length()-1)); 616 name = name.substring(0, i).trim(); 617 this.simpleClassName = simpleClassName(name); 618 this.fullClassName = name; 619 this.value = value; 620 } 621 622 public boolean matches(Constructor<?> m) { 623 if (m == null) 624 return false; 625 Class<?> c = m.getDeclaringClass(); 626 return 627 classMatches(simpleClassName, fullClassName, c) 628 && (argsMatch(args, m.getParameterTypes())); 629 } 630 631 public OMap asMap() { 632 return OMap.of( 633 "simpleClassName", simpleClassName, 634 "fullClassName", fullClassName, 635 "args", args, 636 "value", value 637 ); 638 } 639 640 @Override 641 public String toString() { 642 return asMap().toString(); 643 } 644 } 645 646 static class FieldEntry<V> { 647 String simpleClassName, fullClassName, fieldName; 648 V value; 649 650 FieldEntry(String name, V value) { 651 int i = name.lastIndexOf('.'); 652 String s1 = name.substring(0, i), s2 = name.substring(i+1); 653 this.simpleClassName = simpleClassName(s1); 654 this.fullClassName = s1; 655 this.fieldName = s2; 656 this.value = value; 657 } 658 659 public boolean matches(Field f) { 660 if (f == null) 661 return false; 662 Class<?> c = f.getDeclaringClass(); 663 return 664 classMatches(simpleClassName, fullClassName, c) 665 && (isEquals(f.getName(), fieldName)); 666 } 667 668 public OMap asMap() { 669 return OMap.of( 670 "simpleClassName", simpleClassName, 671 "fullClassName", fullClassName, 672 "fieldName", fieldName, 673 "value", value 674 ); 675 } 676 677 @Override 678 public String toString() { 679 return asMap().toString(); 680 } 681 } 682 683 static boolean argsMatch(String[] names, Class<?>[] args) { 684 if (names == null) 685 return true; 686 if (names.length != args.length) 687 return false; 688 for (int i = 0; i < args.length; i++) { 689 String n = names[i]; 690 Class<?> a = args[i]; 691 if (! (isEquals(n, a.getSimpleName()) || isEquals(n, a.getName()))) 692 return false; 693 } 694 return true; 695 } 696 697 static String simpleClassName(String name) { 698 int i = name.indexOf('.'); 699 if (i == -1) 700 return name; 701 return null; 702 } 703 704 static boolean classMatches(String simpleName, String fullName, Class<?> c) { 705 // For class org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder 706 // c.getSimpleName() == "Builder" 707 // c.getFullName() == "org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder" 708 // c.getPackage() == "org.apache.juneau.a.rttests" 709 String cSimple = c.getSimpleName(), cFull = c.getName(); 710 if (isEquals(simpleName, cSimple) || isEquals(fullName, cFull)) 711 return true; 712 if ("*".equals(simpleName)) 713 return true; 714 if (cFull.indexOf('$') != -1) { 715 Package p = c.getPackage(); 716 if (p != null) 717 cFull = cFull.substring(p.getName().length() + 1); 718 if (isEquals(simpleName, cFull)) 719 return true; 720 int i = cFull.indexOf('$'); 721 while (i != -1) { 722 cFull = cFull.substring(i+1); 723 if (isEquals(simpleName, cFull)) 724 return true; 725 i = cFull.indexOf('$'); 726 } 727 } 728 return false; 729 } 730 731 @Override /* Object */ 732 public String toString() { 733 return OMap.of( 734 "classEntries", classEntries, 735 "methodEntries", methodEntries, 736 "fieldEntries", fieldEntries, 737 "constructorEntries", constructorEntries 738 ).toString(); 739 } 740}