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