    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}