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    }