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