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