001 // Copyright 2004, 2005, 2006, 2010 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.ioc.util;
016
017 import java.util.HashMap;
018 import java.util.IdentityHashMap;
019 import java.util.List;
020 import java.util.Map;
021
022 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
023 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024
025 /**
026 * Used to "uniquify" names within a given context. A base name is passed in, and the return value is the base name, or
027 * the base name extended with a suffix to make it unique.
028 * <p/>
029 * This class is not threadsafe.
030 */
031
032 public final class IdAllocator
033 {
034 private static final String SEPARATOR = "_";
035
036 /**
037 * Map from allocated id to a generator for names associated with the allocated id.
038 */
039 private final Map<String, NameGenerator> generatorMap;
040
041 private final String namespace;
042
043 /**
044 * Generates unique names with a particular prefix.
045 */
046 private static class NameGenerator implements Cloneable
047 {
048 private final String baseId;
049
050 private int index;
051
052 NameGenerator(String baseId)
053 {
054 this.baseId = baseId + SEPARATOR;
055 }
056
057 public String nextId()
058 {
059 return baseId + index++;
060 }
061
062 /**
063 * Clones this instance, returning an equivalent but separate copy.
064 */
065 @SuppressWarnings(
066 { "CloneDoesntDeclareCloneNotSupportedException" })
067 @Override
068 public NameGenerator clone()
069 {
070 try
071 {
072 return (NameGenerator) super.clone();
073 }
074 catch (CloneNotSupportedException ex)
075 {
076 // Unreachable!
077 throw new RuntimeException(ex);
078 }
079 }
080 }
081
082 /**
083 * Creates a new allocator with no namespace.
084 */
085 public IdAllocator()
086 {
087 this("");
088 }
089
090 /**
091 * Creates a new allocator with the provided namespace.
092 */
093 public IdAllocator(String namespace)
094 {
095 this(namespace, new HashMap<String, NameGenerator>());
096 }
097
098 private IdAllocator(String namespace, Map<String, NameGenerator> generatorMap)
099 {
100 this.namespace = namespace;
101 this.generatorMap = generatorMap;
102 }
103
104 /**
105 * Returns a list of all allocated ids, sorted alphabetically.
106 */
107 public List<String> getAllocatedIds()
108 {
109 return InternalUtils.sortedKeys(generatorMap);
110 }
111
112 /**
113 * Creates a clone of this IdAllocator instance, copying the allocator's namespace and key map.
114 */
115 @SuppressWarnings(
116 { "CloneDoesntCallSuperClone" })
117 @Override
118 public IdAllocator clone()
119 {
120 // Copying the generatorMap is tricky; multiple keys will point to the same NameGenerator
121 // instance. We need to clone the NameGenerators, then build a new map around the clones.
122
123 IdentityHashMap<NameGenerator, NameGenerator> transformMap = new IdentityHashMap<NameGenerator, NameGenerator>();
124
125 for (NameGenerator original : generatorMap.values())
126 {
127 NameGenerator copy = original.clone();
128
129 transformMap.put(original, copy);
130 }
131
132 Map<String, NameGenerator> mapCopy = CollectionFactory.newMap();
133
134 for (String key : generatorMap.keySet())
135 {
136 NameGenerator original = generatorMap.get(key);
137 NameGenerator copy = transformMap.get(original);
138
139 mapCopy.put(key, copy);
140 }
141
142 return new IdAllocator(namespace, mapCopy);
143 }
144
145 /**
146 * Allocates the id. Repeated calls for the same name will return "name", "name_0", "name_1", etc.
147 */
148 public String allocateId(String name)
149 {
150 String key = name + namespace;
151
152 NameGenerator g = generatorMap.get(key);
153 String result;
154
155 if (g == null)
156 {
157 g = new NameGenerator(key);
158 result = key;
159 }
160 else
161 result = g.nextId();
162
163 // Handle the degenerate case, where a base name of the form "foo_0" has been
164 // requested. Skip over any duplicates thus formed.
165
166 while (generatorMap.containsKey(result))
167 result = g.nextId();
168
169 generatorMap.put(result, g);
170
171 return result;
172 }
173
174 /**
175 * Checks to see if a given name has been allocated.
176 */
177 public boolean isAllocated(String name)
178 {
179 return generatorMap.containsKey(name);
180 }
181
182 /**
183 * Clears the allocator, resetting it to freshly allocated state.
184 */
185 public void clear()
186 {
187 generatorMap.clear();
188 }
189 }