    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.internal;
    014
    015import org.apache.tapestry5.*;
    016import org.apache.tapestry5.beanmodel.PropertyConduit;
    017import org.apache.tapestry5.beanmodel.PropertyConduit2;
    018import org.apache.tapestry5.beanmodel.internal.InternalPropertyConduit;
    019import org.apache.tapestry5.commons.Messages;
    020import org.apache.tapestry5.commons.Resource;
    021import org.apache.tapestry5.commons.util.CollectionFactory;
    022import org.apache.tapestry5.commons.util.CommonsUtils;
    023import org.apache.tapestry5.func.Mapper;
    024import org.apache.tapestry5.http.Link;
    025import org.apache.tapestry5.ioc.Orderable;
    026import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    027import org.apache.tapestry5.services.ComponentEventRequestParameters;
    028import org.apache.tapestry5.services.LinkCreationListener;
    029import org.apache.tapestry5.services.LinkCreationListener2;
    030import org.apache.tapestry5.services.PageRenderRequestParameters;
    031import org.apache.tapestry5.services.javascript.StylesheetLink;
    032
    033import java.io.IOException;
    034import java.io.InputStream;
    035import java.io.OutputStream;
    036import java.lang.annotation.Annotation;
    037import java.lang.ref.Reference;
    038import java.lang.reflect.Type;
    039import java.util.Arrays;
    040import java.util.List;
    041import java.util.Map;
    042import java.util.regex.Pattern;
    043
    044/**
    045 * Shared utility methods used by various implementation classes.
    046 */
    047@SuppressWarnings("all")
    048public class TapestryInternalUtils
    049{
    050    private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
    051
    052    private static final int BUFFER_SIZE = 5000;
    053
    054    /**
    055     * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
    056     * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
    057     * following word), thus "user_id" also becomes "User Id".
    058     */
    059    public static String toUserPresentable(String id)
    060    {
    061        return InternalUtils.toUserPresentable(id);
    062    }
    063
    064    public static Map<String, String> mapFromKeysAndValues(String... keysAndValues)
    065    {
    066        Map<String, String> result = CollectionFactory.newMap();
    067
    068        int i = 0;
    069        while (i < keysAndValues.length)
    070        {
    071            String key = keysAndValues[i++];
    072            String value = keysAndValues[i++];
    073
    074            result.put(key, value);
    075        }
    076
    077        return result;
    078    }
    079
    080    /**
    081     * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is
    082     * omitted, then the same value is used for both value and label.
    083     */
    084    public static OptionModel toOptionModel(String input)
    085    {
    086        assert input != null;
    087        int equalsx = input.indexOf('=');
    088
    089        if (equalsx < 0)
    090            return new OptionModelImpl(input);
    091
    092        String value = input.substring(0, equalsx);
    093        String label = input.substring(equalsx + 1);
    094
    095        return new OptionModelImpl(label, value);
    096    }
    097
    098    /**
    099     * Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits
    100     * on commas. Ignores whitespace around commas.
    101     *
    102     * @param input comma seperated list of terms
    103     * @return list of option models
    104     */
    105    public static List<OptionModel> toOptionModels(String input)
    106    {
    107        assert input != null;
    108        List<OptionModel> result = CollectionFactory.newList();
    109
    110        for (String term : input.split(","))
    111            result.add(toOptionModel(term.trim()));
    112
    113        return result;
    114    }
    115
    116    /**
    117     * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups).
    118     *
    119     * See TAP5-2184 for why this ends up causing some trouble!
    120     */
    121    public static SelectModel toSelectModel(String input)
    122    {
    123        List<OptionModel> options = toOptionModels(input);
    124
    125        return new SelectModelImpl(null, options);
    126    }
    127
    128    /**
    129     * Converts a map entry to an {@link OptionModel}.
    130     */
    131    public static OptionModel toOptionModel(Map.Entry input)
    132    {
    133        assert input != null;
    134        String label = input.getValue() != null ? String.valueOf(input.getValue()) : "";
    135
    136        return new OptionModelImpl(label, input.getKey());
    137    }
    138
    139    /**
    140     * Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}.
    141     *
    142     * @param input map of elements
    143     * @return list of option models
    144     */
    145    public static <K, V> List<OptionModel> toOptionModels(Map<K, V> input)
    146    {
    147        assert input != null;
    148        List<OptionModel> result = CollectionFactory.newList();
    149
    150        for (Map.Entry entry : input.entrySet())
    151            result.add(toOptionModel(entry));
    152
    153        return result;
    154    }
    155
    156    /**
    157     * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups).
    158     */
    159    public static <K, V> SelectModel toSelectModel(Map<K, V> input)
    160    {
    161        List<OptionModel> options = toOptionModels(input);
    162
    163        return new SelectModelImpl(null, options);
    164    }
    165
    166    /**
    167     * Converts an object to an {@link OptionModel}.
    168     */
    169    public static OptionModel toOptionModel(Object input)
    170    {
    171        String label = (input != null ? String.valueOf(input) : "");
    172
    173        return new OptionModelImpl(label, input);
    174    }
    175
    176    /**
    177     * Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}.
    178     *
    179     * @param input list of elements
    180     * @return list of option models
    181     */
    182    public static <E> List<OptionModel> toOptionModels(List<E> input)
    183    {
    184        assert input != null;
    185        List<OptionModel> result = CollectionFactory.newList();
    186
    187        for (E element : input)
    188            result.add(toOptionModel(element));
    189
    190        return result;
    191    }
    192
    193    /**
    194     * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups).
    195     */
    196    public static <E> SelectModel toSelectModel(List<E> input)
    197    {
    198        List<OptionModel> options = toOptionModels(input);
    199
    200        return new SelectModelImpl(null, options);
    201    }
    202
    203    /**
    204     * Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are
    205     * trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
    206     */
    207    public static KeyValue parseKeyValue(String input)
    208    {
    209        int pos = input.indexOf('=');
    210
    211        if (pos < 1)
    212            throw new IllegalArgumentException(String.format("Key/value pair '%s' is not properly formatted (it does not contain an equals sign).", input));
    213
    214        String key = input.substring(0, pos);
    215        String value = input.substring(pos + 1);
    216
    217        return new KeyValue(key.trim(), value.trim());
    218    }
    219
    220    /**
    221     * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
    222     * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
    223     * underscore).
    224     *
    225     * @param expression a property expression
    226     * @return the expression with punctuation removed
    227     */
    228    public static String extractIdFromPropertyExpression(String expression)
    229    {
    230        return InternalUtils.extractIdFromPropertyExpression(expression);
    231    }
    232
    233    /**
    234     * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
    235     * user presentable form.
    236     */
    237    public static String defaultLabel(String id, Messages messages, String propertyExpression)
    238    {
    239        return InternalUtils.defaultLabel(id, messages, propertyExpression);
    240    }
    241
    242    /**
    243     * Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that
    244     * expression, by locating the last period ('.') in the string.
    245     */
    246    public static String lastTerm(String input)
    247    {
    248        int dotx = input.lastIndexOf('.');
    249
    250        return input.substring(dotx + 1);
    251    }
    252
    253    /**
    254     * Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class
    255     * attribute value.
    256     *
    257     * @param classes classes to combine
    258     * @return the joined classes, or null if classes is empty
    259     */
    260    public static String toClassAttributeValue(List<String> classes)
    261    {
    262        if (classes.isEmpty())
    263            return null;
    264
    265        return InternalUtils.join(classes, " ");
    266    }
    267
    268    /**
    269     * Converts an enum to a label string, allowing for overrides from a message catalog.
    270     *
    271     * <ul>
    272     * <li>As key <em>prefix</em>.<em>name</em> if present. Ex: "ElementType.LOCAL_VARIABLE"
    273     * <li>As key <em>name</em> if present, i.e., "LOCAL_VARIABLE".
    274     * <li>As a user-presentable version of the name, i.e., "Local Variable".
    275     * </ul>
    276     *
    277     * @param messages the messages to search for the label
    278     * @param prefix   prepended to key
    279     * @param value    to get a label for
    280     * @return the label
    281     */
    282    public static String getLabelForEnum(Messages messages, String prefix, Enum value)
    283    {
    284        String name = value.name();
    285
    286        String key = prefix + "." + name;
    287
    288        if (messages.contains(key))
    289            return messages.get(key);
    290
    291        if (messages.contains(name))
    292            return messages.get(name);
    293
    294        return toUserPresentable(name.toLowerCase());
    295    }
    296
    297    public static String getLabelForEnum(Messages messages, Enum value)
    298    {
    299        String prefix = lastTerm(value.getClass().getName());
    300
    301        return getLabelForEnum(messages, prefix, value);
    302    }
    303
    304    private static String replace(String input, Pattern pattern, String replacement)
    305    {
    306        return InternalUtils.replace(input, pattern, replacement);
    307    }
    308
    309    /**
    310     * Determines if the two values are equal. They are equal if they are the exact same value (including if they are
    311     * both null). Otherwise standard equals() comparison is used.
    312     *
    313     * @param left  value to compare, possibly null
    314     * @param right value to compare, possibly null
    315     * @return true if same value, both null, or equal
    316     */
    317    public static <T> boolean isEqual(T left, T right)
    318    {
    319        if (left == right)
    320            return true;
    321
    322        if (left == null)
    323            return false;
    324
    325        return left.equals(right);
    326    }
    327
    328    /**
    329     * Splits a path at each slash.
    330     */
    331    public static String[] splitPath(String path)
    332    {
    333        return CommonsUtils.splitPath(path);
    334    }
    335
    336    /**
    337     * Splits a value around commas. Whitespace around the commas is removed, as is leading and trailing whitespace.
    338     *
    339     * @since 5.1.0.0
    340     */
    341    public static String[] splitAtCommas(String value)
    342    {
    343        return CommonsUtils.splitAtCommas(value);
    344    }
    345
    346    /**
    347     * Copies some content from an input stream to an output stream. It is the caller's responsibility to close the
    348     * streams.
    349     *
    350     * @param in  source of data
    351     * @param out sink of data
    352     * @throws IOException
    353     * @since 5.1.0.0
    354     */
    355    public static void copy(InputStream in, OutputStream out) throws IOException
    356    {
    357        byte[] buffer = new byte[BUFFER_SIZE];
    358
    359        while (true)
    360        {
    361            int length = in.read(buffer);
    362
    363            if (length < 0)
    364                break;
    365
    366            out.write(buffer, 0, length);
    367        }
    368
    369        // TAPESTRY-2415: WebLogic needs this flush() call.
    370        out.flush();
    371    }
    372
    373    public static boolean isEqual(EventContext left, EventContext right)
    374    {
    375        if (left == right)
    376            return true;
    377
    378        int count = left.getCount();
    379
    380        if (count != right.getCount())
    381            return false;
    382
    383        for (int i = 0; i < count; i++)
    384        {
    385            if (!left.get(Object.class, i).equals(right.get(Object.class, i)))
    386                return false;
    387        }
    388
    389        return true;
    390    }
    391
    392    public static InternalPropertyConduit toInternalPropertyConduit(final PropertyConduit conduit)
    393    {
    394        if (conduit instanceof InternalPropertyConduit)
    395            return (InternalPropertyConduit) conduit;
    396
    397        return new InternalPropertyConduit()
    398        {
    399
    400            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    401            {
    402                return conduit.getAnnotation(annotationClass);
    403            }
    404
    405            public void set(Object instance, Object value)
    406            {
    407                conduit.set(instance, value);
    408            }
    409
    410            public Class getPropertyType()
    411            {
    412                return conduit.getPropertyType();
    413            }
    414
    415            public Type getPropertyGenericType()
    416            {
    417                if (conduit instanceof PropertyConduit2)
    418                {
    419                    return ((PropertyConduit2) conduit).getPropertyGenericType();
    420                }
    421                return conduit.getPropertyType();
    422            }
    423
    424            public Object get(Object instance)
    425            {
    426                return conduit.get(instance);
    427            }
    428
    429            public String getPropertyName()
    430            {
    431                return null;
    432            }
    433        };
    434    }
    435
    436    /**
    437     * @param mixinDef the original mixin definition.
    438     * @return an Orderable whose id is the mixin name.
    439     */
    440    public static Orderable<String> mixinTypeAndOrder(String mixinDef)
    441    {
    442        int idx = mixinDef.indexOf("::");
    443        if (idx == -1)
    444        {
    445            return new Orderable<String>(mixinDef, mixinDef);
    446        }
    447        String type = mixinDef.substring(0, idx);
    448        String[] constraints = splitMixinConstraints(mixinDef.substring(idx + 2));
    449
    450        return new Orderable<String>(type, type, constraints);
    451    }
    452
    453    public static String[] splitMixinConstraints(String s)
    454    {
    455        return InternalUtils.isBlank(s) ? null : s.split(";");
    456    }
    457
    458    /**
    459     * Common mapper, used primarily with {@link org.apache.tapestry5.func.Flow#map(org.apache.tapestry5.func.Mapper)}
    460     *
    461     * @since 5.2.0
    462     */
    463    public static Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>()
    464    {
    465        public StylesheetLink map(Asset input)
    466        {
    467            return new StylesheetLink(input);
    468        }
    469    };
    470
    471    public static LinkCreationListener2 toLinkCreationListener2(final LinkCreationListener delegate)
    472    {
    473        return new LinkCreationListener2()
    474        {
    475
    476            public void createdPageRenderLink(Link link, PageRenderRequestParameters parameters)
    477            {
    478                delegate.createdPageRenderLink(link);
    479            }
    480
    481            public void createdComponentEventLink(Link link, ComponentEventRequestParameters parameters)
    482            {
    483                delegate.createdComponentEventLink(link);
    484            }
    485        };
    486    }
    487
    488    /**
    489     * @since 5.3
    490     */
    491    public static String toFileSuffix(String fileName)
    492    {
    493        int dotx = fileName.lastIndexOf('.');
    494
    495        return dotx < 0 ? "" : fileName.substring(dotx + 1);
    496    }
    497
    498    /**
    499     * Extracts a value from a  map of references. Handles the case where the reference does not exist,
    500     * and the case where the reference itself now contains null.
    501     *
    502     * @since 5.3
    503     */
    504    public static <K, V> V getAndDeref(Map<K, ? extends Reference<V>> map, K key)
    505    {
    506        Reference<V> ref = map.get(key);
    507
    508        return ref == null ? null : ref.get();
    509    }
    510
    511    /**
    512     * Gathers together an array containing all the threads.
    513     * @since 5.4 */
    514    public static Thread[] getAllThreads() {
    515        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
    516
    517        while (true) {
    518            ThreadGroup parentGroup = rootGroup.getParent();
    519            if (parentGroup == null) {
    520                break;
    521            }
    522            rootGroup = parentGroup;
    523        }
    524
    525        Thread[] threads = new Thread[rootGroup.activeCount()];
    526
    527        while (true) {
    528            // A really ugly API. threads.length must be larger than
    529            // the actual number of threads, just so we can determine
    530            // if we're done.
    531            int count = rootGroup.enumerate(threads, true);
    532            if (count < threads.length) {
    533                return Arrays.copyOf(threads, count);
    534            }
    535            threads = new Thread[threads.length * 2];
    536        }
    537    }
    538}
    539