001// Copyright 2006-2014 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// 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 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.ioc.internal.util; 016 017import org.apache.tapestry5.func.F; 018import org.apache.tapestry5.func.Mapper; 019import org.apache.tapestry5.func.Predicate; 020import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 021import org.apache.tapestry5.ioc.*; 022import org.apache.tapestry5.ioc.annotations.*; 023import org.apache.tapestry5.ioc.def.*; 024import org.apache.tapestry5.ioc.internal.ServiceDefImpl; 025import org.apache.tapestry5.ioc.services.Coercion; 026import org.apache.tapestry5.ioc.util.ExceptionUtils; 027import org.apache.tapestry5.plastic.PlasticUtils; 028import org.apache.tapestry5.ioc.services.PlasticProxyFactory; 029import org.slf4j.Logger; 030 031import javax.annotation.PostConstruct; 032import javax.inject.Named; 033 034import java.io.Closeable; 035import java.io.IOException; 036import java.lang.annotation.Annotation; 037import java.lang.annotation.Retention; 038import java.lang.annotation.RetentionPolicy; 039import java.lang.reflect.*; 040import java.net.URL; 041import java.util.*; 042import java.util.concurrent.atomic.AtomicLong; 043import java.util.regex.Matcher; 044import java.util.regex.Pattern; 045 046/** 047 * Utilities used within various internal implementations of the tapestry-ioc module. 048 */ 049@SuppressWarnings("all") 050public class InternalUtils 051{ 052 /** 053 * @since 5.2.2 054 */ 055 public static final boolean SERVICE_CLASS_RELOADING_ENABLED = Boolean.parseBoolean(System.getProperty( 056 IOCConstants.SERVICE_CLASS_RELOADING_ENABLED, "true")); 057 058 /** 059 * Converts a method to a user presentable string using a {@link PlasticProxyFactory} to obtain a {@link Location} 060 * (where possible). {@link #asString(Method)} is used under the covers, to present a detailed, but not excessive, 061 * description of the class, method and parameters. 062 * 063 * @param method 064 * method to convert to a string 065 * @param proxyFactory 066 * used to obtain the {@link Location} 067 * @return the method formatted for presentation to the user 068 */ 069 public static String asString(Method method, PlasticProxyFactory proxyFactory) 070 { 071 Location location = proxyFactory.getMethodLocation(method); 072 073 return location != null ? location.toString() : asString(method); 074 } 075 076 /** 077 * Converts a method to a user presentable string consisting of the containing class name, the method name, and the 078 * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). 079 * 080 * @param method 081 * @return short string representation 082 */ 083 public static String asString(Method method) 084 { 085 return InternalCommonsUtils.asString(method); 086 } 087 088 /** 089 * Returns the size of an object array, or null if the array is empty. 090 */ 091 092 public static int size(Object[] array) 093 { 094 return array == null ? 0 : array.length; 095 } 096 097 public static int size(Collection collection) 098 { 099 return collection == null ? 0 : collection.size(); 100 } 101 102 /** 103 * Strips leading "_" and "$" and trailing "_" from the name. 104 */ 105 public static String stripMemberName(String memberName) 106 { 107 return InternalCommonsUtils.stripMemberName(memberName); 108 } 109 110 /** 111 * Converts an enumeration (of Strings) into a sorted list of Strings. 112 */ 113 public static List<String> toList(Enumeration e) 114 { 115 List<String> result = CollectionFactory.newList(); 116 117 while (e.hasMoreElements()) 118 { 119 String name = (String) e.nextElement(); 120 121 result.add(name); 122 } 123 124 Collections.sort(result); 125 126 return result; 127 } 128 129 /** 130 * Finds a specific annotation type within an array of annotations. 131 * 132 * @param <T> 133 * @param annotations 134 * to search 135 * @param annotationClass 136 * to match 137 * @return the annotation instance, if found, or null otherwise 138 */ 139 public static <T extends Annotation> T findAnnotation(Annotation[] annotations, Class<T> annotationClass) 140 { 141 for (Annotation a : annotations) 142 { 143 if (annotationClass.isInstance(a)) 144 return annotationClass.cast(a); 145 } 146 147 return null; 148 } 149 150 private static ObjectCreator<Object> asObjectCreator(final Object fixedValue) 151 { 152 return new ObjectCreator<Object>() 153 { 154 @Override 155 public Object createObject() 156 { 157 return fixedValue; 158 } 159 }; 160 } 161 162 private static ObjectCreator calculateInjection(final Class injectionType, Type genericType, final Annotation[] annotations, 163 final ObjectLocator locator, InjectionResources resources) 164 { 165 final AnnotationProvider provider = new AnnotationProvider() 166 { 167 @Override 168 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 169 { 170 return findAnnotation(annotations, annotationClass); 171 } 172 }; 173 174 // At some point, it would be nice to eliminate InjectService, and rely 175 // entirely on service interface type and point-of-injection markers. 176 177 InjectService is = provider.getAnnotation(InjectService.class); 178 179 if (is != null) 180 { 181 String serviceId = is.value(); 182 183 return asObjectCreator(locator.getService(serviceId, injectionType)); 184 } 185 186 Named named = provider.getAnnotation(Named.class); 187 188 if (named != null) 189 { 190 return asObjectCreator(locator.getService(named.value(), injectionType)); 191 } 192 193 // In the absence of @InjectService, try some autowiring. First, does the 194 // parameter type match one of the resources (the parameter defaults)? 195 196 if (provider.getAnnotation(Inject.class) == null) 197 { 198 Object result = resources.findResource(injectionType, genericType); 199 200 if (result != null) 201 { 202 return asObjectCreator(result); 203 } 204 } 205 206 // TAP5-1765: For @Autobuild, special case where we always compute a fresh value 207 // for the injection on every use. Elsewhere, we compute once when generating the 208 // construction plan and just use the singleton value repeatedly. 209 210 if (provider.getAnnotation(Autobuild.class) != null) 211 { 212 return new ObjectCreator() 213 { 214 @Override 215 public Object createObject() 216 { 217 return locator.getObject(injectionType, provider); 218 } 219 }; 220 } 221 222 // Otherwise, make use of the MasterObjectProvider service to resolve this type (plus 223 // any other information gleaned from additional annotation) into the correct object. 224 225 return asObjectCreator(locator.getObject(injectionType, provider)); 226 } 227 228 public static ObjectCreator[] calculateParametersForMethod(Method method, ObjectLocator locator, 229 InjectionResources resources, OperationTracker tracker) 230 { 231 232 return calculateParameters(locator, resources, method.getParameterTypes(), method.getGenericParameterTypes(), 233 method.getParameterAnnotations(), tracker); 234 } 235 236 public static ObjectCreator[] calculateParameters(final ObjectLocator locator, final InjectionResources resources, 237 Class[] parameterTypes, final Type[] genericTypes, Annotation[][] parameterAnnotations, 238 OperationTracker tracker) 239 { 240 int parameterCount = parameterTypes.length; 241 242 ObjectCreator[] parameters = new ObjectCreator[parameterCount]; 243 244 for (int i = 0; i < parameterCount; i++) 245 { 246 final Class type = parameterTypes[i]; 247 final Type genericType = genericTypes[i]; 248 final Annotation[] annotations = parameterAnnotations[i]; 249 250 String description = String.format("Determining injection value for parameter #%d (%s)", i + 1, 251 PlasticUtils.toTypeName(type)); 252 253 final Invokable<ObjectCreator> operation = new Invokable<ObjectCreator>() 254 { 255 @Override 256 public ObjectCreator invoke() 257 { 258 return calculateInjection(type, genericType, annotations, locator, resources); 259 } 260 }; 261 262 parameters[i] = tracker.invoke(description, operation); 263 } 264 265 return parameters; 266 } 267 268 /** 269 * Injects into the fields (of all visibilities) when the {@link org.apache.tapestry5.ioc.annotations.Inject} or 270 * {@link org.apache.tapestry5.ioc.annotations.InjectService} annotations are present. 271 * 272 * @param object 273 * to be initialized 274 * @param locator 275 * used to resolve external dependencies 276 * @param resources 277 * provides injection resources for fields 278 * @param tracker 279 * track operations 280 */ 281 public static void injectIntoFields(final Object object, final ObjectLocator locator, 282 final InjectionResources resources, OperationTracker tracker) 283 { 284 Class clazz = object.getClass(); 285 286 while (clazz != Object.class) 287 { 288 Field[] fields = clazz.getDeclaredFields(); 289 290 for (final Field f : fields) 291 { 292 // Ignore all static and final fields. 293 294 int fieldModifiers = f.getModifiers(); 295 296 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) 297 continue; 298 299 final AnnotationProvider ap = new AnnotationProvider() 300 { 301 @Override 302 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 303 { 304 return f.getAnnotation(annotationClass); 305 } 306 }; 307 308 String description = String.format("Calculating possible injection value for field %s.%s (%s)", 309 clazz.getName(), f.getName(), 310 PlasticUtils.toTypeName(f.getType())); 311 312 tracker.run(description, new Runnable() 313 { 314 @Override 315 public void run() 316 { 317 final Class<?> fieldType = f.getType(); 318 319 InjectService is = ap.getAnnotation(InjectService.class); 320 if (is != null) 321 { 322 inject(object, f, locator.getService(is.value(), fieldType)); 323 return; 324 } 325 326 if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) 327 { 328 Object value = resources.findResource(fieldType, f.getGenericType()); 329 330 if (value != null) 331 { 332 inject(object, f, value); 333 return; 334 } 335 336 inject(object, f, locator.getObject(fieldType, ap)); 337 return; 338 } 339 340 if (ap.getAnnotation(javax.inject.Inject.class) != null) 341 { 342 Named named = ap.getAnnotation(Named.class); 343 344 if (named == null) 345 { 346 Object value = resources.findResource(fieldType, f.getGenericType()); 347 348 if (value != null) 349 { 350 inject(object, f, value); 351 return; 352 } 353 354 inject(object, f, locator.getObject(fieldType, ap)); 355 } else 356 { 357 inject(object, f, locator.getService(named.value(), fieldType)); 358 } 359 360 return; 361 } 362 363 // Ignore fields that do not have the necessary annotation. 364 365 } 366 }); 367 } 368 369 clazz = clazz.getSuperclass(); 370 } 371 } 372 373 private synchronized static void inject(Object target, Field field, Object value) 374 { 375 try 376 { 377 if (!field.isAccessible()) 378 field.setAccessible(true); 379 380 field.set(target, value); 381 382 // Is there a need to setAccessible back to false? 383 } catch (Exception ex) 384 { 385 throw new RuntimeException(String.format("Unable to set field '%s' of %s to %s: %s", field.getName(), 386 target, value, ExceptionUtils.toMessage(ex))); 387 } 388 } 389 390 /** 391 * Joins together some number of elements to form a comma separated list. 392 */ 393 public static String join(List elements) 394 { 395 return InternalCommonsUtils.join(elements); 396 } 397 398 /** 399 * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the 400 * string "(blank)". 401 * 402 * @param elements 403 * objects to be joined together 404 * @param separator 405 * used between elements when joining 406 */ 407 public static String join(List elements, String separator) 408 { 409 return InternalCommonsUtils.join(elements, separator); 410 } 411 412 /** 413 * Creates a sorted copy of the provided elements, then turns that into a comma separated list. 414 * 415 * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or 416 * empty 417 */ 418 public static String joinSorted(Collection elements) 419 { 420 return InternalCommonsUtils.joinSorted(elements); 421 } 422 423 /** 424 * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). 425 */ 426 427 public static boolean isBlank(String input) 428 { 429 return InternalCommonsUtils.isBlank(input); 430 } 431 432 /** 433 * Returns true if the input is an empty collection. 434 */ 435 436 public static boolean isEmptyCollection(Object input) 437 { 438 if (input instanceof Collection) 439 { 440 return ((Collection) input).isEmpty(); 441 } 442 443 return false; 444 } 445 446 public static boolean isNonBlank(String input) 447 { 448 return InternalCommonsUtils.isNonBlank(input); 449 } 450 451 /** 452 * Capitalizes a string, converting the first character to uppercase. 453 */ 454 public static String capitalize(String input) 455 { 456 return InternalCommonsUtils.capitalize(input); 457 } 458 459 /** 460 * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not 461 * convertable to a location. 462 */ 463 464 public static Location locationOf(Object location) 465 { 466 return InternalCommonsUtils.locationOf(location); 467 } 468 469 public static <K, V> Set<K> keys(Map<K, V> map) 470 { 471 if (map == null) 472 return Collections.emptySet(); 473 474 return map.keySet(); 475 } 476 477 /** 478 * Gets a value from a map (which may be null). 479 * 480 * @param <K> 481 * @param <V> 482 * @param map 483 * the map to extract from (may be null) 484 * @param key 485 * @return the value from the map, or null if the map is null 486 */ 487 488 public static <K, V> V get(Map<K, V> map, K key) 489 { 490 if (map == null) 491 return null; 492 493 return map.get(key); 494 } 495 496 /** 497 * Returns true if the method provided is a static method. 498 */ 499 public static boolean isStatic(Method method) 500 { 501 return Modifier.isStatic(method.getModifiers()); 502 } 503 504 public static <T> Iterator<T> reverseIterator(final List<T> list) 505 { 506 final ListIterator<T> normal = list.listIterator(list.size()); 507 508 return new Iterator<T>() 509 { 510 @Override 511 public boolean hasNext() 512 { 513 return normal.hasPrevious(); 514 } 515 516 @Override 517 public T next() 518 { 519 return normal.previous(); 520 } 521 522 @Override 523 public void remove() 524 { 525 throw new UnsupportedOperationException(); 526 } 527 }; 528 } 529 530 /** 531 * Return true if the input string contains the marker for symbols that must be expanded. 532 */ 533 public static boolean containsSymbols(String input) 534 { 535 return InternalCommonsUtils.containsSymbols(input); 536 } 537 538 /** 539 * Searches the string for the final period ('.') character and returns everything after that. The input string is 540 * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property 541 * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period 542 * character. 543 */ 544 public static String lastTerm(String input) 545 { 546 return InternalCommonsUtils.lastTerm(input); 547 } 548 549 /** 550 * Searches a class for the "best" constructor, the public constructor with the most parameters. Returns null if 551 * there are no public constructors. If there is more than one constructor with the maximum number of parameters, it 552 * is not determined which will be returned (don't build a class like that!). In addition, if a constructor is 553 * annotated with {@link org.apache.tapestry5.ioc.annotations.Inject}, it will be used (no check for multiple such 554 * constructors is made, only at most a single constructor should have the annotation). 555 * 556 * @param clazz 557 * to search for a constructor for 558 * @return the constructor to be used to instantiate the class, or null if no appropriate constructor was found 559 */ 560 public static Constructor findAutobuildConstructor(Class clazz) 561 { 562 Constructor[] constructors = clazz.getConstructors(); 563 564 switch (constructors.length) 565 { 566 case 1: 567 568 return constructors[0]; 569 570 case 0: 571 572 return null; 573 574 default: 575 break; 576 } 577 578 Constructor standardConstructor = findConstructorByAnnotation(constructors, Inject.class); 579 Constructor javaxConstructor = findConstructorByAnnotation(constructors, javax.inject.Inject.class); 580 581 if (standardConstructor != null && javaxConstructor != null) 582 throw new IllegalArgumentException( 583 String.format( 584 "Too many autobuild constructors found: use either @%s or @%s annotation to mark a single constructor for autobuilding.", 585 Inject.class.getName(), javax.inject.Inject.class.getName())); 586 587 if (standardConstructor != null) 588 { 589 return standardConstructor; 590 } 591 592 if (javaxConstructor != null) 593 { 594 return javaxConstructor; 595 } 596 597 // Choose a constructor with the most parameters. 598 599 Comparator<Constructor> comparator = new Comparator<Constructor>() 600 { 601 @Override 602 public int compare(Constructor o1, Constructor o2) 603 { 604 return o2.getParameterTypes().length - o1.getParameterTypes().length; 605 } 606 }; 607 608 Arrays.sort(constructors, comparator); 609 610 return constructors[0]; 611 } 612 613 private static <T extends Annotation> Constructor findConstructorByAnnotation(Constructor[] constructors, 614 Class<T> annotationClass) 615 { 616 for (Constructor c : constructors) 617 { 618 if (c.getAnnotation(annotationClass) != null) 619 return c; 620 } 621 622 return null; 623 } 624 625 /** 626 * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map 627 * that allows multiple values for the same key. 628 * 629 * @param map 630 * to store value into 631 * @param key 632 * for which a value is added 633 * @param value 634 * to add 635 * @param <K> 636 * the type of key 637 * @param <V> 638 * the type of the list 639 */ 640 public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value) 641 { 642 InternalCommonsUtils.addToMapList(map, key, value); 643 } 644 645 /** 646 * Validates that the marker annotation class had a retention policy of runtime. 647 * 648 * @param markerClass 649 * the marker annotation class 650 */ 651 public static void validateMarkerAnnotation(Class markerClass) 652 { 653 Retention policy = (Retention) markerClass.getAnnotation(Retention.class); 654 655 if (policy != null && policy.value() == RetentionPolicy.RUNTIME) 656 return; 657 658 throw new IllegalArgumentException(UtilMessages.badMarkerAnnotation(markerClass)); 659 } 660 661 public static void validateMarkerAnnotations(Class[] markerClasses) 662 { 663 for (Class markerClass : markerClasses) 664 validateMarkerAnnotation(markerClass); 665 } 666 667 public static void close(Closeable stream) 668 { 669 if (stream != null) 670 try 671 { 672 stream.close(); 673 } catch (IOException ex) 674 { 675 // Ignore. 676 } 677 } 678 679 /** 680 * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name. 681 * 682 * @param exception 683 * to extract message from 684 * @return message or class name 685 * @deprecated Deprecated in 5.4; use {@link ExceptionUtils#toMessage(Throwable)} instead. 686 */ 687 // Cause it gets used a lot outside of Tapestry proper even though it is internal. 688 public static String toMessage(Throwable exception) 689 { 690 return ExceptionUtils.toMessage(exception); 691 } 692 693 public static void validateConstructorForAutobuild(Constructor constructor) 694 { 695 Class clazz = constructor.getDeclaringClass(); 696 697 if (!Modifier.isPublic(clazz.getModifiers())) 698 throw new IllegalArgumentException(String.format( 699 "Class %s is not a public class and may not be autobuilt.", clazz.getName())); 700 701 if (!Modifier.isPublic(constructor.getModifiers())) 702 throw new IllegalArgumentException( 703 String.format( 704 "Constructor %s is not public and may not be used for autobuilding an instance of the class. " 705 + "You should make the constructor public, or mark an alternate public constructor with the @Inject annotation.", 706 constructor)); 707 } 708 709 /** 710 * @since 5.3 711 */ 712 public static final Mapper<Class, AnnotationProvider> CLASS_TO_AP_MAPPER = new Mapper<Class, AnnotationProvider>() 713 { 714 @Override 715 public AnnotationProvider map(final Class element) 716 { 717 return toAnnotationProvider(element); 718 } 719 720 }; 721 722 /** 723 * @since 5.3 724 */ 725 public static AnnotationProvider toAnnotationProvider(final Class element) 726 { 727 return InternalCommonsUtils.toAnnotationProvider(element); 728 } 729 730 /** 731 * @since 5.3 732 */ 733 public static final Mapper<Method, AnnotationProvider> METHOD_TO_AP_MAPPER = new Mapper<Method, AnnotationProvider>() 734 { 735 @Override 736 public AnnotationProvider map(final Method element) 737 { 738 return toAnnotationProvider(element); 739 } 740 }; 741 742 public static final Method findMethod(Class containingClass, String methodName, Class... parameterTypes) 743 { 744 if (containingClass == null) 745 return null; 746 747 try 748 { 749 return containingClass.getMethod(methodName, parameterTypes); 750 } catch (SecurityException ex) 751 { 752 throw new RuntimeException(ex); 753 } catch (NoSuchMethodException ex) 754 { 755 return null; 756 } 757 } 758 759 /** 760 * @since 5.3 761 */ 762 public static ServiceDef3 toServiceDef3(ServiceDef sd) 763 { 764 if (sd instanceof ServiceDef3) 765 return (ServiceDef3) sd; 766 767 final ServiceDef2 sd2 = toServiceDef2(sd); 768 769 return new ServiceDef3() 770 { 771 // ServiceDef3 methods: 772 773 @Override 774 public AnnotationProvider getClassAnnotationProvider() 775 { 776 return toAnnotationProvider(getServiceInterface()); 777 } 778 779 @Override 780 public AnnotationProvider getMethodAnnotationProvider(final String methodName, final Class... argumentTypes) 781 { 782 return toAnnotationProvider(findMethod(getServiceInterface(), methodName, argumentTypes)); 783 } 784 785 @Override 786 public Class getServiceImplementation() 787 { 788 return null; 789 } 790 791 // ServiceDef2 methods: 792 793 @Override 794 public boolean isPreventDecoration() 795 { 796 return sd2.isPreventDecoration(); 797 } 798 799 @Override 800 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 801 { 802 return sd2.createServiceCreator(resources); 803 } 804 805 @Override 806 public String getServiceId() 807 { 808 return sd2.getServiceId(); 809 } 810 811 @Override 812 public Set<Class> getMarkers() 813 { 814 return sd2.getMarkers(); 815 } 816 817 @Override 818 public Class getServiceInterface() 819 { 820 return sd2.getServiceInterface(); 821 } 822 823 @Override 824 public String getServiceScope() 825 { 826 return sd2.getServiceScope(); 827 } 828 829 @Override 830 public boolean isEagerLoad() 831 { 832 return sd2.isEagerLoad(); 833 } 834 835 }; 836 } 837 838 public static ServiceDef2 toServiceDef2(final ServiceDef sd) 839 { 840 if (sd instanceof ServiceDef2) 841 return (ServiceDef2) sd; 842 843 return new ServiceDef2() 844 { 845 // ServiceDef2 methods: 846 847 @Override 848 public boolean isPreventDecoration() 849 { 850 return false; 851 } 852 853 // ServiceDef methods: 854 855 @Override 856 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 857 { 858 return sd.createServiceCreator(resources); 859 } 860 861 @Override 862 public String getServiceId() 863 { 864 return sd.getServiceId(); 865 } 866 867 @Override 868 public Set<Class> getMarkers() 869 { 870 return sd.getMarkers(); 871 } 872 873 @Override 874 public Class getServiceInterface() 875 { 876 return sd.getServiceInterface(); 877 } 878 879 @Override 880 public String getServiceScope() 881 { 882 return sd.getServiceScope(); 883 } 884 885 @Override 886 public boolean isEagerLoad() 887 { 888 return sd.isEagerLoad(); 889 } 890 891 @Override 892 public String toString() 893 { 894 return sd.toString(); 895 } 896 897 @Override 898 public int hashCode() 899 { 900 final int prime = 31; 901 int result = 1; 902 result = prime * result + ((getServiceId() == null) ? 0 : getServiceId().hashCode()); 903 return result; 904 } 905 906 @Override 907 public boolean equals(Object obj) 908 { 909 if (this == obj) { return true; } 910 if (obj == null) { return false; } 911 if (!(obj instanceof ServiceDefImpl)) { return false; } 912 ServiceDef other = (ServiceDef) obj; 913 if (getServiceId() == null) 914 { 915 if (other.getServiceId() != null) { return false; } 916 } 917 else if (!getServiceId().equals(other.getServiceId())) { return false; } 918 return true; 919 } 920 921 }; 922 } 923 924 public static ModuleDef2 toModuleDef2(final ModuleDef md) 925 { 926 if (md instanceof ModuleDef2) 927 return (ModuleDef2) md; 928 929 return new ModuleDef2() 930 { 931 @Override 932 public Set<AdvisorDef> getAdvisorDefs() 933 { 934 return Collections.emptySet(); 935 } 936 937 @Override 938 public Class getBuilderClass() 939 { 940 return md.getBuilderClass(); 941 } 942 943 @Override 944 public Set<ContributionDef> getContributionDefs() 945 { 946 return md.getContributionDefs(); 947 } 948 949 @Override 950 public Set<DecoratorDef> getDecoratorDefs() 951 { 952 return md.getDecoratorDefs(); 953 } 954 955 @Override 956 public String getLoggerName() 957 { 958 return md.getLoggerName(); 959 } 960 961 @Override 962 public ServiceDef getServiceDef(String serviceId) 963 { 964 return md.getServiceDef(serviceId); 965 } 966 967 @Override 968 public Set<String> getServiceIds() 969 { 970 return md.getServiceIds(); 971 } 972 973 @Override 974 public Set<StartupDef> getStartups() 975 { 976 return Collections.emptySet(); 977 } 978 }; 979 } 980 981 /** 982 * @since 5.1.0.2 983 */ 984 public static ServiceLifecycle2 toServiceLifecycle2(final ServiceLifecycle lifecycle) 985 { 986 if (lifecycle instanceof ServiceLifecycle2) 987 return (ServiceLifecycle2) lifecycle; 988 989 return new ServiceLifecycle2() 990 { 991 @Override 992 public boolean requiresProxy() 993 { 994 return true; 995 } 996 997 @Override 998 public Object createService(ServiceResources resources, ObjectCreator creator) 999 { 1000 return lifecycle.createService(resources, creator); 1001 } 1002 1003 @Override 1004 public boolean isSingleton() 1005 { 1006 return lifecycle.isSingleton(); 1007 } 1008 }; 1009 } 1010 1011 /** 1012 * @since 5.2.0 1013 */ 1014 public static <T extends Comparable<T>> List<T> matchAndSort(Collection<? extends T> collection, 1015 Predicate<T> predicate) 1016 { 1017 assert predicate != null; 1018 1019 List<T> result = CollectionFactory.newList(); 1020 1021 for (T object : collection) 1022 { 1023 if (predicate.accept(object)) 1024 result.add(object); 1025 } 1026 1027 Collections.sort(result); 1028 1029 return result; 1030 } 1031 1032 /** 1033 * @since 5.2.0 1034 */ 1035 public static ContributionDef2 toContributionDef2(final ContributionDef contribution) 1036 { 1037 if (contribution instanceof ContributionDef2) 1038 return (ContributionDef2) contribution; 1039 1040 return new ContributionDef2() 1041 { 1042 1043 @Override 1044 public Set<Class> getMarkers() 1045 { 1046 return Collections.emptySet(); 1047 } 1048 1049 @Override 1050 public Class getServiceInterface() 1051 { 1052 return null; 1053 } 1054 1055 @Override 1056 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1057 Configuration configuration) 1058 { 1059 contribution.contribute(moduleSource, resources, configuration); 1060 } 1061 1062 @Override 1063 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1064 OrderedConfiguration configuration) 1065 { 1066 contribution.contribute(moduleSource, resources, configuration); 1067 } 1068 1069 @Override 1070 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 1071 MappedConfiguration configuration) 1072 { 1073 contribution.contribute(moduleSource, resources, configuration); 1074 } 1075 1076 @Override 1077 public String getServiceId() 1078 { 1079 return contribution.getServiceId(); 1080 } 1081 1082 @Override 1083 public String toString() 1084 { 1085 return contribution.toString(); 1086 } 1087 }; 1088 } 1089 1090 public static ContributionDef3 toContributionDef3(ContributionDef contribution) 1091 { 1092 1093 if (contribution instanceof ContributionDef2) 1094 { 1095 return (ContributionDef3) contribution; 1096 } 1097 1098 final ContributionDef2 cd2 = toContributionDef2(contribution); 1099 1100 return new ContributionDef3() 1101 { 1102 @Override 1103 public boolean isOptional() 1104 { 1105 return false; 1106 } 1107 1108 @Override 1109 public String getServiceId() 1110 { 1111 return cd2.getServiceId(); 1112 } 1113 1114 @Override 1115 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, Configuration configuration) 1116 { 1117 cd2.contribute(moduleSource, resources, configuration); 1118 } 1119 1120 @Override 1121 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, OrderedConfiguration configuration) 1122 { 1123 cd2.contribute(moduleSource, resources, configuration); 1124 } 1125 1126 @Override 1127 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, MappedConfiguration configuration) 1128 { 1129 cd2.contribute(moduleSource, resources, configuration); 1130 } 1131 1132 @Override 1133 public Set<Class> getMarkers() 1134 { 1135 return cd2.getMarkers(); 1136 } 1137 1138 @Override 1139 public Class getServiceInterface() 1140 { 1141 return cd2.getServiceInterface(); 1142 } 1143 1144 @Override 1145 public String toString() 1146 { 1147 return cd2.toString(); 1148 } 1149 }; 1150 } 1151 1152 /** 1153 * @since 5.2.2 1154 */ 1155 public static AdvisorDef2 toAdvisorDef2(final AdvisorDef advisor) 1156 { 1157 if (advisor instanceof AdvisorDef2) 1158 return (AdvisorDef2) advisor; 1159 1160 return new AdvisorDef2() 1161 { 1162 1163 @Override 1164 public ServiceAdvisor createAdvisor(ModuleBuilderSource moduleSource, ServiceResources resources) 1165 { 1166 return advisor.createAdvisor(moduleSource, resources); 1167 } 1168 1169 @Override 1170 public String getAdvisorId() 1171 { 1172 return advisor.getAdvisorId(); 1173 } 1174 1175 @Override 1176 public String[] getConstraints() 1177 { 1178 return advisor.getConstraints(); 1179 } 1180 1181 @Override 1182 public boolean matches(ServiceDef serviceDef) 1183 { 1184 return advisor.matches(serviceDef); 1185 } 1186 1187 @Override 1188 public Set<Class> getMarkers() 1189 { 1190 return Collections.emptySet(); 1191 } 1192 1193 @Override 1194 public Class getServiceInterface() 1195 { 1196 return null; 1197 } 1198 1199 @Override 1200 public String toString() 1201 { 1202 return advisor.toString(); 1203 } 1204 }; 1205 } 1206 1207 /** 1208 * @since 5.2.2 1209 */ 1210 public static DecoratorDef2 toDecoratorDef2(final DecoratorDef decorator) 1211 { 1212 if (decorator instanceof DecoratorDef2) 1213 return (DecoratorDef2) decorator; 1214 1215 return new DecoratorDef2() 1216 { 1217 1218 @Override 1219 public ServiceDecorator createDecorator(ModuleBuilderSource moduleSource, ServiceResources resources) 1220 { 1221 return decorator.createDecorator(moduleSource, resources); 1222 } 1223 1224 @Override 1225 public String[] getConstraints() 1226 { 1227 return decorator.getConstraints(); 1228 } 1229 1230 @Override 1231 public String getDecoratorId() 1232 { 1233 return decorator.getDecoratorId(); 1234 } 1235 1236 @Override 1237 public boolean matches(ServiceDef serviceDef) 1238 { 1239 return decorator.matches(serviceDef); 1240 } 1241 1242 @Override 1243 public Set<Class> getMarkers() 1244 { 1245 return Collections.emptySet(); 1246 } 1247 1248 @Override 1249 public Class getServiceInterface() 1250 { 1251 return null; 1252 } 1253 1254 @Override 1255 public String toString() 1256 { 1257 return decorator.toString(); 1258 } 1259 }; 1260 } 1261 1262 /** 1263 * Determines if the indicated class is stored as a locally accessible file 1264 * (and not, typically, as a file inside a JAR). This is related to automatic 1265 * reloading of services. 1266 * 1267 * @since 5.2.0 1268 */ 1269 public static boolean isLocalFile(Class clazz) 1270 { 1271 String path = PlasticInternalUtils.toClassPath(clazz.getName()); 1272 1273 ClassLoader loader = clazz.getClassLoader(); 1274 1275 // System classes have no visible class loader, and are not local files. 1276 1277 if (loader == null) 1278 return false; 1279 1280 URL classFileURL = loader.getResource(path); 1281 1282 return classFileURL != null && classFileURL.getProtocol().equals("file"); 1283 } 1284 1285 /** 1286 * Wraps a {@link Coercion} as a {@link Mapper}. 1287 * 1288 * @since 5.2.0 1289 */ 1290 public static <S, T> Mapper<S, T> toMapper(final Coercion<S, T> coercion) 1291 { 1292 assert coercion != null; 1293 1294 return new Mapper<S, T>() 1295 { 1296 @Override 1297 public T map(S value) 1298 { 1299 return coercion.coerce(value); 1300 } 1301 }; 1302 } 1303 1304 private static final AtomicLong uuidGenerator = new AtomicLong(System.nanoTime()); 1305 1306 /** 1307 * Generates a unique value for the current execution of the application. This initial UUID value 1308 * is not easily predictable; subsequent UUIDs are allocated in ascending series. 1309 * 1310 * @since 5.2.0 1311 */ 1312 public static long nextUUID() 1313 { 1314 return uuidGenerator.incrementAndGet(); 1315 } 1316 1317 /** 1318 * Extracts the service id from the passed annotated element. First the {@link ServiceId} annotation is checked. 1319 * If present, its value is returned. Otherwise {@link Named} annotation is checked. If present, its value is 1320 * returned. 1321 * If neither of the annotations is present, <code>null</code> value is returned 1322 * 1323 * @param annotated 1324 * annotated element to get annotations from 1325 * @since 5.3 1326 */ 1327 public static String getServiceId(AnnotatedElement annotated) 1328 { 1329 ServiceId serviceIdAnnotation = annotated.getAnnotation(ServiceId.class); 1330 1331 if (serviceIdAnnotation != null) 1332 { 1333 return serviceIdAnnotation.value(); 1334 } 1335 1336 Named namedAnnotation = annotated.getAnnotation(Named.class); 1337 1338 if (namedAnnotation != null) 1339 { 1340 String value = namedAnnotation.value(); 1341 1342 if (InternalCommonsUtils.isNonBlank(value)) 1343 { 1344 return value; 1345 } 1346 } 1347 1348 return null; 1349 } 1350 1351 1352 public static AnnotationProvider toAnnotationProvider(final Method element) 1353 { 1354 return InternalCommonsUtils.toAnnotationProvider(element); 1355 } 1356 1357 public static <T> ObjectCreator<T> createConstructorConstructionPlan(final OperationTracker tracker, final ObjectLocator locator, 1358 final InjectionResources resources, 1359 final Logger logger, 1360 final String description, 1361 final Constructor<T> constructor) 1362 { 1363 return tracker.invoke(String.format("Creating plan to instantiate %s via %s", 1364 constructor.getDeclaringClass().getName(), 1365 constructor), new Invokable<ObjectCreator<T>>() 1366 { 1367 @Override 1368 public ObjectCreator<T> invoke() 1369 { 1370 validateConstructorForAutobuild(constructor); 1371 1372 ObjectCreator[] constructorParameters = calculateParameters(locator, resources, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), tracker); 1373 1374 Invokable<T> core = new ConstructorInvoker<T>(constructor, constructorParameters); 1375 1376 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); 1377 1378 ConstructionPlan<T> plan = new ConstructionPlan(tracker, description, wrapped); 1379 1380 extendPlanForInjectedFields(plan, tracker, locator, resources, constructor.getDeclaringClass()); 1381 1382 extendPlanForPostInjectionMethods(plan, tracker, locator, resources, constructor.getDeclaringClass()); 1383 1384 return plan; 1385 } 1386 }); 1387 } 1388 1389 private static <T> void extendPlanForInjectedFields(final ConstructionPlan<T> plan, OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, Class<T> instantiatedClass) 1390 { 1391 Class clazz = instantiatedClass; 1392 1393 while (clazz != Object.class) 1394 { 1395 Field[] fields = clazz.getDeclaredFields(); 1396 1397 for (final Field f : fields) 1398 { 1399 // Ignore all static and final fields. 1400 1401 int fieldModifiers = f.getModifiers(); 1402 1403 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) 1404 continue; 1405 1406 final AnnotationProvider ap = new AnnotationProvider() 1407 { 1408 @Override 1409 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 1410 { 1411 return f.getAnnotation(annotationClass); 1412 } 1413 }; 1414 1415 String description = String.format("Calculating possible injection value for field %s.%s (%s)", 1416 clazz.getName(), f.getName(), 1417 PlasticUtils.toTypeName(f.getType())); 1418 1419 tracker.run(description, new Runnable() 1420 { 1421 @Override 1422 public void run() 1423 { 1424 final Class<?> fieldType = f.getType(); 1425 1426 InjectService is = ap.getAnnotation(InjectService.class); 1427 if (is != null) 1428 { 1429 addInjectPlan(plan, f, locator.getService(is.value(), fieldType)); 1430 return; 1431 } 1432 1433 if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) 1434 { 1435 Object value = resources.findResource(fieldType, f.getGenericType()); 1436 1437 if (value != null) 1438 { 1439 addInjectPlan(plan, f, value); 1440 return; 1441 } 1442 1443 addInjectPlan(plan, f, locator.getObject(fieldType, ap)); 1444 return; 1445 } 1446 1447 if (ap.getAnnotation(javax.inject.Inject.class) != null) 1448 { 1449 Named named = ap.getAnnotation(Named.class); 1450 1451 if (named == null) 1452 { 1453 addInjectPlan(plan, f, locator.getObject(fieldType, ap)); 1454 } else 1455 { 1456 addInjectPlan(plan, f, locator.getService(named.value(), fieldType)); 1457 } 1458 1459 return; 1460 } 1461 1462 // Ignore fields that do not have the necessary annotation. 1463 1464 } 1465 }); 1466 } 1467 1468 clazz = clazz.getSuperclass(); 1469 } 1470 } 1471 1472 private static <T> void addInjectPlan(ConstructionPlan<T> plan, final Field field, final Object injectedValue) 1473 { 1474 plan.add(new InitializationPlan<T>() 1475 { 1476 @Override 1477 public String getDescription() 1478 { 1479 return String.format("Injecting %s into field %s of class %s.", 1480 injectedValue, 1481 field.getName(), 1482 field.getDeclaringClass().getName()); 1483 } 1484 1485 @Override 1486 public void initialize(T instance) 1487 { 1488 inject(instance, field, injectedValue); 1489 } 1490 }); 1491 } 1492 1493 private static boolean hasAnnotation(AccessibleObject member, Class<? extends Annotation> annotationType) 1494 { 1495 return member.getAnnotation(annotationType) != null; 1496 } 1497 1498 private static <T> void extendPlanForPostInjectionMethods(ConstructionPlan<T> plan, OperationTracker tracker, ObjectLocator locator, InjectionResources resources, Class<T> instantiatedClass) 1499 { 1500 for (Method m : instantiatedClass.getMethods()) 1501 { 1502 if (hasAnnotation(m, PostInjection.class) || hasAnnotation(m, PostConstruct.class)) 1503 { 1504 extendPlanForPostInjectionMethod(plan, tracker, locator, resources, m); 1505 } 1506 } 1507 } 1508 1509 private static void extendPlanForPostInjectionMethod(final ConstructionPlan<?> plan, final OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, final Method method) 1510 { 1511 tracker.run("Computing parameters for post-injection method " + method, 1512 new Runnable() 1513 { 1514 @Override 1515 public void run() 1516 { 1517 final ObjectCreator[] parameters = calculateParametersForMethod(method, locator, 1518 resources, tracker); 1519 1520 plan.add(new InitializationPlan<Object>() 1521 { 1522 @Override 1523 public String getDescription() 1524 { 1525 return "Invoking " + method; 1526 } 1527 1528 @Override 1529 public void initialize(Object instance) 1530 { 1531 Throwable fail = null; 1532 1533 Object[] realized = realizeObjects(parameters); 1534 1535 try 1536 { 1537 method.invoke(instance, realized); 1538 } catch (InvocationTargetException ex) 1539 { 1540 fail = ex.getTargetException(); 1541 } catch (Exception ex) 1542 { 1543 fail = ex; 1544 } 1545 1546 if (fail != null) 1547 { 1548 throw new RuntimeException(String 1549 .format("Exception invoking method %s: %s", method, ExceptionUtils.toMessage(fail)), fail); 1550 } 1551 } 1552 }); 1553 } 1554 }); 1555 } 1556 1557 1558 public static <T> ObjectCreator<T> createMethodInvocationPlan(final OperationTracker tracker, final ObjectLocator locator, 1559 final InjectionResources resources, 1560 final Logger logger, 1561 final String description, 1562 final Object instance, 1563 final Method method) 1564 { 1565 1566 return tracker.invoke("Creating plan to invoke " + method, new Invokable<ObjectCreator<T>>() 1567 { 1568 @Override 1569 public ObjectCreator<T> invoke() 1570 { 1571 ObjectCreator[] methodParameters = calculateParametersForMethod(method, locator, resources, tracker); 1572 1573 Invokable<T> core = new MethodInvoker<T>(instance, method, methodParameters); 1574 1575 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); 1576 1577 return new ConstructionPlan(tracker, description, wrapped); 1578 } 1579 }); 1580 } 1581 1582 /** 1583 * @since 5.3.1, 5.4 1584 */ 1585 public final static Mapper<ObjectCreator, Object> CREATE_OBJECT = new Mapper<ObjectCreator, Object>() 1586 { 1587 @Override 1588 public Object map(ObjectCreator element) 1589 { 1590 return element.createObject(); 1591 } 1592 }; 1593 1594 /** 1595 * @since 5.3.1, 5.4 1596 */ 1597 public static Object[] realizeObjects(ObjectCreator[] creators) 1598 { 1599 return F.flow(creators).map(CREATE_OBJECT).toArray(Object.class); 1600 } 1601 1602 /** 1603 * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings. 1604 * 1605 * @param map 1606 * the map to extract keys from (may be null) 1607 * @return the sorted keys, or the empty set if map is null 1608 */ 1609 1610 public static List<String> sortedKeys(Map map) 1611 { 1612 return InternalCommonsUtils.sortedKeys(map); 1613 } 1614 1615 /** 1616 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case 1617 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the 1618 * following word), thus "user_id" also becomes "User Id". 1619 */ 1620 public static String toUserPresentable(String id) 1621 { 1622 return InternalCommonsUtils.toUserPresentable(id); 1623 } 1624 1625 /** 1626 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages, 1627 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the 1628 * underscore). 1629 * 1630 * @param expression a property expression 1631 * @return the expression with punctuation removed 1632 */ 1633 public static String extractIdFromPropertyExpression(String expression) 1634 { 1635 return InternalCommonsUtils.extractIdFromPropertyExpression(expression); 1636 } 1637 1638 /** 1639 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a 1640 * user presentable form. 1641 */ 1642 public static String defaultLabel(String id, Messages messages, String propertyExpression) 1643 { 1644 return InternalCommonsUtils.defaultLabel(id, messages, propertyExpression); 1645 } 1646 1647 public static String replace(String input, Pattern pattern, String replacement) 1648 { 1649 return InternalCommonsUtils.replace(input, pattern, replacement); 1650 } 1651 1652}