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 }