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 }