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 }