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