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