    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.
    014package org.apache.tapestry5.ioc.internal.util;
    015
    016import java.lang.annotation.Annotation;
    017import java.lang.reflect.Method;
    018import java.util.Collection;
    019import java.util.Collections;
    020import java.util.List;
    021import java.util.Map;
    022import java.util.regex.Matcher;
    023import java.util.regex.Pattern;
    024
    025import org.apache.tapestry5.ioc.AnnotationProvider;
    026import org.apache.tapestry5.ioc.Locatable;
    027import org.apache.tapestry5.ioc.Location;
    028import org.apache.tapestry5.ioc.Messages;
    029import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
    030
    031/**
    032 * Utility methods class for the Commons package.
    033 */
    034public class InternalCommonsUtils {
    035
    036    /**
    037     * @since 5.3
    038     */
    039    public final static AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider();
    040    private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
    041
    042    /**
    043     * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map
    044     * that allows multiple values for the same key.
    045     *
    046     * @param map
    047     *         to store value into
    048     * @param key
    049     *         for which a value is added
    050     * @param value
    051     *         to add
    052     * @param <K>
    053     *         the type of key
    054     * @param <V>
    055     *         the type of the list
    056     */
    057    public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value)
    058    {
    059        List<V> list = map.get(key);
    060
    061        if (list == null)
    062        {
    063            list = CollectionFactory.newList();
    064            map.put(key, list);
    065        }
    066
    067        list.add(value);
    068    }
    069
    070    /**
    071     * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not
    072     * convertable to a location.
    073     */
    074
    075    public static Location locationOf(Object location)
    076    {
    077        if (location == null)
    078            return null;
    079
    080        if (location instanceof Location)
    081            return (Location) location;
    082
    083        if (location instanceof Locatable)
    084            return ((Locatable) location).getLocation();
    085
    086        return null;
    087    }
    088
    089    public static AnnotationProvider toAnnotationProvider(final Method element)
    090    {
    091        if (element == null)
    092            return NULL_ANNOTATION_PROVIDER;
    093
    094        return new AnnotationProvider()
    095        {
    096            @Override
    097            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    098            {
    099                return element.getAnnotation(annotationClass);
    100            }
    101        };
    102    }
    103
    104    /**
    105     * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
    106     * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
    107     * underscore).
    108     *
    109     * @param expression a property expression
    110     * @return the expression with punctuation removed
    111     */
    112    public static String extractIdFromPropertyExpression(String expression)
    113    {
    114        return replace(expression, NON_WORD_PATTERN, "");
    115    }
    116
    117    public static String replace(String input, Pattern pattern, String replacement)
    118    {
    119        return pattern.matcher(input).replaceAll(replacement);
    120    }
    121
    122    /**
    123     * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
    124     * user presentable form.
    125     */
    126    public static String defaultLabel(String id, Messages messages, String propertyExpression)
    127    {
    128        String key = id + "-label";
    129
    130        if (messages.contains(key))
    131            return messages.get(key);
    132
    133        return toUserPresentable(extractIdFromPropertyExpression(InternalCommonsUtils.lastTerm(propertyExpression)));
    134    }
    135
    136    /**
    137     * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
    138     * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
    139     * following word), thus "user_id" also becomes "User Id".
    140     */
    141    public static String toUserPresentable(String id)
    142    {
    143        StringBuilder builder = new StringBuilder(id.length() * 2);
    144
    145        char[] chars = id.toCharArray();
    146        boolean postSpace = true;
    147        boolean upcaseNext = true;
    148
    149        for (char ch : chars)
    150        {
    151            if (upcaseNext)
    152            {
    153                builder.append(Character.toUpperCase(ch));
    154                upcaseNext = false;
    155
    156                continue;
    157            }
    158
    159            if (ch == '_')
    160            {
    161                builder.append(' ');
    162                upcaseNext = true;
    163                continue;
    164            }
    165
    166            boolean upperCase = Character.isUpperCase(ch);
    167
    168            if (upperCase && !postSpace)
    169                builder.append(' ');
    170
    171            builder.append(ch);
    172
    173            postSpace = upperCase;
    174        }
    175
    176        return builder.toString();
    177    }
    178
    179    /**
    180     * @since 5.3
    181     */
    182    public static AnnotationProvider toAnnotationProvider(final Class element)
    183    {
    184        return new AnnotationProvider()
    185        {
    186            @Override
    187            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    188            {
    189                return annotationClass.cast(element.getAnnotation(annotationClass));
    190            }
    191        };
    192    }
    193
    194    /**
    195     * Pattern used to eliminate leading and trailing underscores and dollar signs.
    196     */
    197    static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$",
    198            Pattern.CASE_INSENSITIVE);
    199
    200    /**
    201     * Converts a method to a user presentable string consisting of the containing class name, the method name, and the
    202     * short form of the parameter list (the class name of each parameter type, shorn of the package name portion).
    203     *
    204     * @param method
    205     * @return short string representation
    206     */
    207    public static String asString(Method method)
    208    {
    209        StringBuilder buffer = new StringBuilder();
    210
    211        buffer.append(method.getDeclaringClass().getName());
    212        buffer.append('.');
    213        buffer.append(method.getName());
    214        buffer.append('(');
    215
    216        for (int i = 0; i < method.getParameterTypes().length; i++)
    217        {
    218            if (i > 0)
    219                buffer.append(", ");
    220
    221            String name = method.getParameterTypes()[i].getSimpleName();
    222
    223            buffer.append(name);
    224        }
    225
    226        return buffer.append(')').toString();
    227    }
    228
    229    /**
    230     * Strips leading "_" and "$" and trailing "_" from the name.
    231     */
    232    public static String stripMemberName(String memberName)
    233    {
    234        assert InternalCommonsUtils.isNonBlank(memberName);
    235        Matcher matcher = NAME_PATTERN.matcher(memberName);
    236
    237        if (!matcher.matches())
    238            throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName));
    239
    240        return matcher.group(1);
    241    }
    242
    243    /**
    244     * Joins together some number of elements to form a comma separated list.
    245     */
    246    public static String join(List elements)
    247    {
    248        return InternalCommonsUtils.join(elements, ", ");
    249    }
    250
    251    /**
    252     * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the
    253     * string "(blank)".
    254     *
    255     * @param elements
    256     *         objects to be joined together
    257     * @param separator
    258     *         used between elements when joining
    259     */
    260    public static String join(List elements, String separator)
    261    {
    262        switch (elements.size())
    263        {
    264            case 0:
    265                return "";
    266
    267            case 1:
    268                return elements.get(0).toString();
    269
    270            default:
    271
    272                StringBuilder buffer = new StringBuilder();
    273                boolean first = true;
    274
    275                for (Object o : elements)
    276                {
    277                    if (!first)
    278                        buffer.append(separator);
    279
    280                    String string = String.valueOf(o);
    281
    282                    if (string.equals(""))
    283                        string = "(blank)";
    284
    285                    buffer.append(string);
    286
    287                    first = false;
    288                }
    289
    290                return buffer.toString();
    291        }
    292    }
    293
    294    /**
    295     * Creates a sorted copy of the provided elements, then turns that into a comma separated list.
    296     *
    297     * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or
    298     *         empty
    299     */
    300    public static String joinSorted(Collection elements)
    301    {
    302        if (elements == null || elements.isEmpty())
    303            return "(none)";
    304
    305        List<String> list = CollectionFactory.newList();
    306
    307        for (Object o : elements)
    308            list.add(String.valueOf(o));
    309
    310        Collections.sort(list);
    311
    312        return join(list);
    313    }
    314
    315    /**
    316     * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
    317     */
    318
    319    public static boolean isBlank(String input)
    320    {
    321        return input == null || input.length() == 0 || input.trim().length() == 0;
    322    }
    323
    324    /**
    325     * Capitalizes a string, converting the first character to uppercase.
    326     */
    327    public static String capitalize(String input)
    328    {
    329        if (input.length() == 0)
    330            return input;
    331
    332        return input.substring(0, 1).toUpperCase() + input.substring(1);
    333    }
    334
    335    public static boolean isNonBlank(String input)
    336    {
    337        return !isBlank(input);
    338    }
    339
    340    /**
    341     * Return true if the input string contains the marker for symbols that must be expanded.
    342     */
    343    public static boolean containsSymbols(String input)
    344    {
    345        return input.contains("${");
    346    }
    347
    348    /**
    349     * Searches the string for the final period ('.') character and returns everything after that. The input string is
    350     * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property
    351     * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period
    352     * character.
    353     */
    354    public static String lastTerm(String input)
    355    {
    356        assert isNonBlank(input);
    357        int dotx = input.lastIndexOf('.');
    358
    359        if (dotx < 0)
    360            return input;
    361
    362        return input.substring(dotx + 1);
    363    }
    364
    365    /**
    366     * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings.
    367     *
    368     * @param map
    369     *         the map to extract keys from (may be null)
    370     * @return the sorted keys, or the empty set if map is null
    371     */
    372
    373    public static List<String> sortedKeys(Map map)
    374    {
    375        if (map == null)
    376            return Collections.emptyList();
    377
    378        List<String> keys = CollectionFactory.newList();
    379
    380        for (Object o : map.keySet())
    381            keys.add(String.valueOf(o));
    382
    383        Collections.sort(keys);
    384
    385        return keys;
    386    }
    387
    388}