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 }