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 }