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
015package org.apache.tapestry5.internal.util;
016
017import org.apache.commons.collections.map.CaseInsensitiveMap;
018import org.apache.tapestry5.func.F;
019import org.apache.tapestry5.func.Worker;
020import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
021import org.apache.tapestry5.ioc.internal.util.InternalUtils;
022import org.apache.tapestry5.ioc.internal.util.LockSupport;
023
024import java.util.Collections;
025import java.util.Set;
026import 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 */
039public 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}