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