001 // Copyright 2004, 2005 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.tapestry.util; 016 017 import org.apache.hivemind.ApplicationRuntimeException; 018 import org.apache.hivemind.util.Defense; 019 import org.apache.tapestry.TapestryUtils; 020 021 import java.util.ArrayList; 022 import java.util.HashMap; 023 import java.util.List; 024 import java.util.Map; 025 026 /** 027 * Used to "uniquify" names within a given context. A base name is passed in, 028 * and the return value is the base name, or the base name extended with a 029 * suffix to make it unique. 030 * 031 * @author Howard Lewis Ship 032 * @since 3.0 033 */ 034 035 public class IdAllocator 036 { 037 038 private static final String SEPARATOR = "_"; 039 040 private static final char NAME_SEPARATOR = '$'; 041 042 final Map _generatorMap = new HashMap(); 043 final List _uniqueGenerators = new ArrayList(); 044 045 final String _namespace; 046 047 /** Class used only by IdAllocator. */ 048 private class NameGenerator implements Cloneable 049 { 050 private final String _baseId; 051 052 private int _index; 053 054 NameGenerator(String baseId) 055 { 056 _baseId = baseId + SEPARATOR; 057 } 058 059 NameGenerator(String baseId, int index) 060 { 061 _baseId = baseId + SEPARATOR; 062 _index = index; 063 } 064 065 public String nextId() 066 { 067 return _baseId + _index++; 068 } 069 070 public String peekId() 071 { 072 return _baseId + _index; 073 } 074 075 public String getBaseId() 076 { 077 return _baseId.substring(0, _baseId.length() - 1); 078 } 079 080 /** 081 * {@inheritDoc} 082 */ 083 protected Object clone() 084 throws CloneNotSupportedException 085 { 086 return super.clone(); 087 } 088 089 public boolean equals(Object o) 090 { 091 if (this == o) 092 return true; 093 if (!(o instanceof NameGenerator)) 094 return false; 095 096 NameGenerator that = (NameGenerator) o; 097 098 if (_baseId != null ? !_baseId.equals(that._baseId) : that._baseId != null) 099 return false; 100 101 return true; 102 } 103 104 public int hashCode() 105 { 106 int result; 107 result = (_baseId != null ? _baseId.hashCode() : 0); 108 result = 31 * result + _index; 109 return result; 110 } 111 112 public String toString() 113 { 114 return "NameGenerator[" + 115 "_baseId='" + _baseId + '\'' + 116 '\n' + 117 ", _index=" + _index + 118 '\n' + 119 ']'; 120 } 121 } 122 123 public IdAllocator() 124 { 125 this(""); 126 } 127 128 public IdAllocator(String namespace) 129 { 130 Defense.notNull(namespace, "namespace"); 131 132 _namespace = namespace; 133 } 134 135 /** 136 * Allocates the id. Repeated calls for the same name will return "name", 137 * "name_0", "name_1", etc. 138 * 139 * @param name 140 * The base id to allocate new unique ids from. 141 * 142 * @return A unique version of the passed in id. 143 */ 144 145 public String allocateId(String name) 146 { 147 String key = name + _namespace; 148 149 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 150 String result = null; 151 152 if (g == null) 153 { 154 g = new NameGenerator(key); 155 _uniqueGenerators.add(g); 156 result = key; 157 } 158 else 159 result = g.nextId(); 160 161 // Handle the degenerate case, where a base name of the form "foo$0" has 162 // been 163 // requested. Skip over any duplicates thus formed. 164 165 while(_generatorMap.containsKey(result.toLowerCase())) 166 result = g.nextId(); 167 168 _generatorMap.put(result.toLowerCase(), g); 169 170 return result; 171 } 172 173 /** 174 * Should return the exact same thing as {@link #allocateId(String)}, with the difference 175 * that the calculated id is not allocated and stored so multiple calls will always return the 176 * same thing. 177 * 178 * @param name The name to peek at. 179 * @return The next id that will be allocated for the given name. 180 */ 181 public String peekNextId(String name) 182 { 183 String key = name + _namespace; 184 185 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 186 String result = null; 187 188 if (g == null) 189 { 190 g = new NameGenerator(key); 191 result = key; 192 } else 193 result = g.peekId(); 194 195 // Handle the degenerate case, where a base name of the form "foo_0" has 196 // been 197 // requested. Skip over any duplicates thus formed. 198 199 // in a peek we don't want to actually increment any id state so we must 200 // clone 201 202 if (_generatorMap.containsKey(result.toLowerCase())) 203 { 204 try { 205 NameGenerator cg = (NameGenerator)g.clone(); 206 207 while (_generatorMap.containsKey(result.toLowerCase())) 208 result = cg.nextId(); 209 210 } catch (CloneNotSupportedException e) { 211 throw new ApplicationRuntimeException(e); 212 } 213 } 214 215 return result; 216 } 217 218 /** 219 * Creates a custom string representation of the current state of this instance, capable 220 * of being re-created by using the corresponding {@link IdAllocator#fromExternalString(String)} method. 221 * 222 * @return The external string representation of the current state of this instance. 223 */ 224 public String toExternalString() 225 { 226 StringBuffer str = new StringBuffer(_namespace); 227 228 for (int i=0; i < _uniqueGenerators.size(); i++) 229 { 230 // namespace is always the first element, so safe to always add comma here 231 232 str.append(","); 233 234 NameGenerator g = (NameGenerator) _uniqueGenerators.get(i); 235 236 str.append(g.getBaseId()).append(NAME_SEPARATOR); 237 str.append(g._index); 238 } 239 240 return str.toString(); 241 } 242 243 /** 244 * Using the base id and index value, re-creates the state of generated id values in the 245 * internal map to what it would have been when generating ids orginally. 246 * 247 * @param baseId The base id being seeded. 248 * @param index The last known index value used for the id. 249 */ 250 void addSeed(String baseId, int index) 251 { 252 NameGenerator g = new NameGenerator(baseId, 0); 253 _uniqueGenerators.add(g); 254 _generatorMap.put(baseId.toLowerCase(), g); 255 256 // add generated key to map until we reach top level index value 257 while(g._index != index) 258 { 259 String nextId = g.nextId().toLowerCase(); 260 261 // only dump value in if not already covered by another allocator 262 263 if (!_generatorMap.containsKey(nextId)) 264 { 265 _generatorMap.put(nextId, g); 266 } 267 } 268 } 269 270 /** 271 * Clears the allocator, resetting it to freshly allocated state. 272 */ 273 274 public void clear() 275 { 276 _generatorMap.clear(); 277 _uniqueGenerators.clear(); 278 } 279 280 public static IdAllocator fromExternalString(String seed) 281 { 282 Defense.notNull(seed, "seed"); 283 284 String[] values = TapestryUtils.split(seed); 285 if (values.length == 0) 286 { 287 return new IdAllocator(); 288 } 289 290 String namespace = values[0]; 291 292 IdAllocator idAllocator = new IdAllocator(namespace); 293 294 for (int i=1; i < values.length; i++) 295 { 296 int index = values[i].lastIndexOf(NAME_SEPARATOR); 297 298 if (index < 0) 299 continue; 300 301 String baseId = values[i].substring(0, index); 302 int valueIndex = Integer.parseInt(values[i].substring(index + 1, values[i].length())); 303 304 idAllocator.addSeed(baseId, valueIndex); 305 } 306 307 return idAllocator; 308 } 309 }