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