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}