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    }