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