    001    // Copyright 2011, 2012 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
    015    package org.apache.tapestry5.internal.util;
    016
    017    import org.apache.commons.collections.map.CaseInsensitiveMap;
    018    import org.apache.tapestry5.func.F;
    019    import org.apache.tapestry5.func.Worker;
    020    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    021    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    022    import org.apache.tapestry5.ioc.internal.util.LockSupport;
    023
    024    import java.util.Collections;
    025    import java.util.Set;
    026    import java.util.concurrent.locks.ReadWriteLock;
    027
    028    /**
    029     * Simple, thread-safe associative array that relates a name to a value. Names are case-insensitive.
    030     * This is optimized to use less memory (than a {@link CaseInsensitiveMap} (it uses a singly-liked list),
    031     * though the cost of a lookup is more expensive. However, this is a good match against many of the structures inside
    032     * a page instance, where most lookups occur only during page constructions, and the number of values is often small.
    033     * <p/>
    034     * Each NameSet has its own {@link ReadWriteLock}.
    035     *
    036     * @param <T>
    037     *         the type of value stored
    038     */
    039    public class NamedSet<T> extends LockSupport
    040    {
    041        private NamedRef<T> first;
    042
    043        private static class NamedRef<T>
    044        {
    045            NamedRef<T> next;
    046
    047            String name;
    048
    049            T value;
    050
    051            public NamedRef(String name, T value)
    052            {
    053                this.name = name;
    054                this.value = value;
    055            }
    056        }
    057
    058        /**
    059         * Returns a set of the names of all stored values.
    060         */
    061        public Set<String> getNames()
    062        {
    063            try
    064            {
    065                acquireReadLock();
    066
    067                Set<String> result = CollectionFactory.newSet();
    068
    069                NamedRef<T> cursor = first;
    070
    071                while (cursor != null)
    072                {
    073                    result.add(cursor.name);
    074                    cursor = cursor.next;
    075                }
    076
    077                return result;
    078            } finally
    079            {
    080                releaseReadLock();
    081            }
    082        }
    083
    084        /**
    085         * Returns a set of all the values in the set.
    086         */
    087        public Set<T> getValues()
    088        {
    089            Set<T> result = CollectionFactory.newSet();
    090
    091            try
    092            {
    093                acquireReadLock();
    094
    095                NamedRef<T> cursor = first;
    096
    097                while (cursor != null)
    098                {
    099                    result.add(cursor.value);
    100                    cursor = cursor.next;
    101                }
    102
    103                return result;
    104            } finally
    105            {
    106                releaseReadLock();
    107            }
    108        }
    109
    110        /**
    111         * Gets the value for the provided name.
    112         *
    113         * @param name
    114         *         used to locate the value
    115         * @return the value, or null if not found
    116         */
    117        public T get(String name)
    118        {
    119            try
    120            {
    121                acquireReadLock();
    122
    123                NamedRef<T> cursor = first;
    124
    125                while (cursor != null)
    126                {
    127                    if (cursor.name.equalsIgnoreCase(name))
    128                    {
    129                        return cursor.value;
    130                    }
    131
    132                    cursor = cursor.next;
    133                }
    134
    135                return null;
    136            } finally
    137            {
    138                releaseReadLock();
    139            }
    140        }
    141
    142        /**
    143         * Stores a new value into the set, replacing any previous value with the same name. Name comparisons are case
    144         * insensitive.
    145         *
    146         * @param name
    147         *         to store the value. May not be blank.
    148         * @param newValue
    149         *         non-null value to store
    150         */
    151        public void put(String name, T newValue)
    152        {
    153            assert InternalUtils.isNonBlank(name);
    154            assert newValue != null;
    155
    156            try
    157            {
    158                takeWriteLock();
    159
    160                NamedRef<T> prev = null;
    161                NamedRef<T> cursor = first;
    162
    163                while (cursor != null)
    164                {
    165                    if (cursor.name.equalsIgnoreCase(name))
    166                    {
    167                        // Retain the case of the name as put(), even if it doesn't match
    168                        // the existing case
    169
    170                        cursor.name = name;
    171                        cursor.value = newValue;
    172
    173                        return;
    174                    }
    175
    176                    prev = cursor;
    177                    cursor = cursor.next;
    178                }
    179
    180                NamedRef<T> newRef = new NamedRef<T>(name, newValue);
    181
    182                if (prev == null)
    183                    first = newRef;
    184                else
    185                    prev.next = newRef;
    186            } finally
    187            {
    188                releaseWriteLock();
    189            }
    190        }
    191
    192        /**
    193         * Iterates over the values, passing each in turn to the supplied worker.
    194         *
    195         * @param worker
    196         *         performs an operation on, or using, the value
    197         */
    198        public void eachValue(Worker<T> worker)
    199        {
    200            F.flow(getValues()).each(worker);
    201        }
    202
    203
    204        /**
    205         * Puts a new value, but only if it does not already exist.
    206         *
    207         * @param name
    208         *         name to store (comparisons are case insensitive) may not be blank
    209         * @param newValue
    210         *         non-null value to store
    211         * @return true if value stored, false if name already exists
    212         */
    213        public boolean putIfNew(String name, T newValue)
    214        {
    215            assert InternalUtils.isNonBlank(name);
    216            assert newValue != null;
    217
    218            try
    219            {
    220                takeWriteLock();
    221
    222                NamedRef<T> prev = null;
    223                NamedRef<T> cursor = first;
    224
    225                while (cursor != null)
    226                {
    227                    if (cursor.name.equalsIgnoreCase(name))
    228                    {
    229                        return false;
    230                    }
    231
    232                    prev = cursor;
    233                    cursor = cursor.next;
    234                }
    235
    236                NamedRef<T> newRef = new NamedRef<T>(name, newValue);
    237
    238                if (prev == null)
    239                    first = newRef;
    240                else
    241                    prev.next = newRef;
    242
    243                return true;
    244            } finally
    245            {
    246                releaseWriteLock();
    247            }
    248        }
    249
    250        /**
    251         * Convenience method for creating a new, empty set.
    252         */
    253        public static <T> NamedSet<T> create()
    254        {
    255            return new NamedSet<T>();
    256        }
    257
    258        /**
    259         * Convenience method for getting a value from a set that may be null.
    260         *
    261         * @param <T>
    262         * @param set
    263         *         set to search, may be null
    264         * @param name
    265         *         name to lookup
    266         * @return value from set, or null if not found, or if set is null
    267         */
    268        public static <T> T get(NamedSet<T> set, String name)
    269        {
    270            return set == null ? null : set.get(name);
    271        }
    272
    273        /**
    274         * Gets the names in the set, returning an empty set if the NamedSet is null.
    275         */
    276        public static Set<String> getNames(NamedSet<?> set)
    277        {
    278            if (set == null)
    279            {
    280                return Collections.emptySet();
    281            }
    282
    283            return set.getNames();
    284        }
    285
    286        /**
    287         * Returns the values in the set, returning an empty set if the NamedSet is null.
    288         */
    289        public static <T> Set<T> getValues(NamedSet<T> set)
    290        {
    291            if (set == null)
    292            {
    293                return Collections.emptySet();
    294            }
    295
    296            return set.getValues();
    297        }
    298    }