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