001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.util; 014 015import org.apache.tapestry5.commons.internal.util.LockSupport; 016import org.apache.tapestry5.commons.util.CaseInsensitiveMap; 017import org.apache.tapestry5.commons.util.CollectionFactory; 018import org.apache.tapestry5.func.F; 019import org.apache.tapestry5.func.Worker; 020import org.apache.tapestry5.ioc.internal.util.InternalUtils; 021 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Set; 025import java.util.concurrent.locks.ReadWriteLock; 026 027/** 028 * Simple, thread-safe associative array that relates a name to a value. Names are case-insensitive. 029 * This is optimized to use less memory (than a {@link CaseInsensitiveMap} (it uses a singly-liked list), 030 * though the cost of a lookup is more expensive. However, this is a good match against many of the structures inside 031 * a page instance, where most lookups occur only during page constructions, and the number of values is often small. 032 * 033 * Each NameSet has its own {@link ReadWriteLock}. 034 * 035 * @param <T> 036 * the type of value stored 037 */ 038public class NamedSet<T> extends LockSupport 039{ 040 private NamedRef<T> first; 041 042 private static class NamedRef<T> 043 { 044 NamedRef<T> next; 045 046 String name; 047 048 T value; 049 050 public NamedRef(String name, T value) 051 { 052 this.name = name; 053 this.value = value; 054 } 055 } 056 057 /** 058 * Returns a set of the names of all stored values. 059 */ 060 public Set<String> getNames() 061 { 062 try 063 { 064 acquireReadLock(); 065 066 Set<String> result = CollectionFactory.newSet(); 067 068 NamedRef<T> cursor = first; 069 070 while (cursor != null) 071 { 072 result.add(cursor.name); 073 cursor = cursor.next; 074 } 075 076 return result; 077 } finally 078 { 079 releaseReadLock(); 080 } 081 } 082 083 /** 084 * Returns a set of all the values in the set. 085 */ 086 public Set<T> getValues() 087 { 088 Set<T> result = CollectionFactory.newSet(); 089 addValues(result); 090 return result; 091 } 092 093 private void addValues(Collection<T> result) 094 { 095 try 096 { 097 acquireReadLock(); 098 099 NamedRef<T> cursor = first; 100 101 while (cursor != null) 102 { 103 result.add(cursor.value); 104 cursor = cursor.next; 105 } 106 107 } finally 108 { 109 releaseReadLock(); 110 } 111 } 112 113 /** 114 * Gets the value for the provided name. 115 * 116 * @param name 117 * used to locate the value 118 * @return the value, or null if not found 119 */ 120 public T get(String name) 121 { 122 try 123 { 124 acquireReadLock(); 125 126 NamedRef<T> cursor = first; 127 128 while (cursor != null) 129 { 130 if (cursor.name.equalsIgnoreCase(name)) 131 { 132 return cursor.value; 133 } 134 135 cursor = cursor.next; 136 } 137 138 return null; 139 } finally 140 { 141 releaseReadLock(); 142 } 143 } 144 145 /** 146 * Stores a new value into the set, replacing any previous value with the same name. Name comparisons are case 147 * insensitive. 148 * 149 * @param name 150 * to store the value. May not be blank. 151 * @param newValue 152 * non-null value to store 153 */ 154 public void put(String name, T newValue) 155 { 156 assert InternalUtils.isNonBlank(name); 157 assert newValue != null; 158 159 try 160 { 161 takeWriteLock(); 162 163 NamedRef<T> prev = null; 164 NamedRef<T> cursor = first; 165 166 while (cursor != null) 167 { 168 if (cursor.name.equalsIgnoreCase(name)) 169 { 170 // Retain the case of the name as put(), even if it doesn't match 171 // the existing case 172 173 cursor.name = name; 174 cursor.value = newValue; 175 176 return; 177 } 178 179 prev = cursor; 180 cursor = cursor.next; 181 } 182 183 NamedRef<T> newRef = new NamedRef<T>(name, newValue); 184 185 if (prev == null) 186 first = newRef; 187 else 188 prev.next = newRef; 189 } finally 190 { 191 releaseWriteLock(); 192 } 193 } 194 195 /** 196 * Iterates over the values, passing each in turn to the supplied worker. 197 * 198 * @param worker 199 * performs an operation on, or using, the value 200 */ 201 public void eachValue(Worker<T> worker) 202 { 203 Collection<T> result = CollectionFactory.newList(); 204 addValues(result); 205 F.flow(result).each(worker); 206 } 207 208 209 /** 210 * Puts a new value, but only if it does not already exist. 211 * 212 * @param name 213 * name to store (comparisons are case insensitive) may not be blank 214 * @param newValue 215 * non-null value to store 216 * @return true if value stored, false if name already exists 217 */ 218 public boolean putIfNew(String name, T newValue) 219 { 220 assert InternalUtils.isNonBlank(name); 221 assert newValue != null; 222 223 try 224 { 225 takeWriteLock(); 226 227 NamedRef<T> prev = null; 228 NamedRef<T> cursor = first; 229 230 while (cursor != null) 231 { 232 if (cursor.name.equalsIgnoreCase(name)) 233 { 234 return false; 235 } 236 237 prev = cursor; 238 cursor = cursor.next; 239 } 240 241 NamedRef<T> newRef = new NamedRef<T>(name, newValue); 242 243 if (prev == null) 244 first = newRef; 245 else 246 prev.next = newRef; 247 248 return true; 249 } finally 250 { 251 releaseWriteLock(); 252 } 253 } 254 255 /** 256 * Convenience method for creating a new, empty set. 257 */ 258 public static <T> NamedSet<T> create() 259 { 260 return new NamedSet<T>(); 261 } 262 263 /** 264 * Convenience method for getting a value from a set that may be null. 265 * 266 * @param <T> 267 * @param set 268 * set to search, may be null 269 * @param name 270 * name to lookup 271 * @return value from set, or null if not found, or if set is null 272 */ 273 public static <T> T get(NamedSet<T> set, String name) 274 { 275 return set == null ? null : set.get(name); 276 } 277 278 /** 279 * Gets the names in the set, returning an empty set if the NamedSet is null. 280 */ 281 public static Set<String> getNames(NamedSet<?> set) 282 { 283 if (set == null) 284 { 285 return Collections.emptySet(); 286 } 287 288 return set.getNames(); 289 } 290 291 /** 292 * Returns the values in the set, returning an empty set if the NamedSet is null. 293 */ 294 public static <T> Set<T> getValues(NamedSet<T> set) 295 { 296 if (set == null) 297 { 298 return Collections.emptySet(); 299 } 300 301 return set.getValues(); 302 } 303}