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