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 }