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 }