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
015package org.apache.tapestry5.ioc.internal.util;
016
017import org.apache.tapestry5.func.F;
018import org.apache.tapestry5.func.Mapper;
019import org.apache.tapestry5.func.Predicate;
020import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
021import org.apache.tapestry5.ioc.*;
022import org.apache.tapestry5.ioc.annotations.*;
023import org.apache.tapestry5.ioc.def.*;
024import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
025import org.apache.tapestry5.ioc.services.Coercion;
026import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
027import org.apache.tapestry5.plastic.MethodAdvice;
028import org.apache.tapestry5.plastic.MethodInvocation;
029import org.apache.tapestry5.plastic.PlasticUtils;
030import org.slf4j.Logger;
031
032import javax.annotation.PostConstruct;
033import javax.inject.Named;
034import java.io.Closeable;
035import java.io.IOException;
036import java.lang.annotation.Annotation;
037import java.lang.annotation.Retention;
038import java.lang.annotation.RetentionPolicy;
039import java.lang.reflect.*;
040import java.net.URL;
041import java.util.*;
042import java.util.concurrent.atomic.AtomicLong;
043import java.util.regex.Matcher;
044import java.util.regex.Pattern;
045
046/**
047 * Utilities used within various internal implementations of the tapestry-ioc module.
048 */
049@SuppressWarnings("all")
050public 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}