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