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