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 020 import java.util.HashMap; 021 import java.util.Map; 022 023 /** 024 * Used to "uniquify" names within a given context. A base name is passed in, 025 * and the return value is the base name, or the base name extended with a 026 * suffix to make it unique. 027 * 028 * @author Howard Lewis Ship 029 * @since 3.0 030 */ 031 032 public class IdAllocator 033 { 034 035 private static final String SEPARATOR = "_"; 036 037 private final Map _generatorMap = new HashMap(); 038 039 private final String _namespace; 040 041 /** Class used only by IdAllocator. */ 042 private class NameGenerator implements Cloneable 043 { 044 045 private final String _baseId; 046 047 private int _index; 048 049 NameGenerator(String baseId) 050 { 051 _baseId = baseId + SEPARATOR; 052 } 053 054 public String nextId() 055 { 056 return _baseId + _index++; 057 } 058 059 public String peekId() 060 { 061 return _baseId + _index; 062 } 063 064 /** 065 * {@inheritDoc} 066 */ 067 protected Object clone() 068 throws CloneNotSupportedException 069 { 070 return super.clone(); 071 } 072 } 073 074 public IdAllocator() 075 { 076 this(""); 077 } 078 079 public IdAllocator(String namespace) 080 { 081 Defense.notNull(namespace, "namespace"); 082 083 _namespace = namespace; 084 } 085 086 /** 087 * Allocates the id. Repeated calls for the same name will return "name", 088 * "name_0", "name_1", etc. 089 * 090 * @param name 091 * The base id to allocate new unique ids from. 092 * 093 * @return A unique version of the passed in id. 094 */ 095 096 public String allocateId(String name) 097 { 098 String key = name + _namespace; 099 100 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 101 String result = null; 102 103 if (g == null) 104 { 105 g = new NameGenerator(key); 106 result = key; 107 } 108 else 109 result = g.nextId(); 110 111 // Handle the degenerate case, where a base name of the form "foo$0" has 112 // been 113 // requested. Skip over any duplicates thus formed. 114 115 while(_generatorMap.containsKey(result.toLowerCase())) 116 result = g.nextId(); 117 118 _generatorMap.put(result.toLowerCase(), g); 119 120 return result; 121 } 122 123 /** 124 * Should return the exact same thing as {@link #allocateId(String)}, with the difference 125 * that the calculated id is not allocated and stored so multiple calls will always return the 126 * same thing. 127 * 128 * @param name The name to peek at. 129 * @return The next id that will be allocated for the given name. 130 */ 131 public String peekNextId(String name) 132 { 133 String key = name + _namespace; 134 135 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 136 String result = null; 137 138 if (g == null) 139 { 140 g = new NameGenerator(key); 141 result = key; 142 } else 143 result = g.peekId(); 144 145 // Handle the degenerate case, where a base name of the form "foo_0" has 146 // been 147 // requested. Skip over any duplicates thus formed. 148 149 // in a peek we don't want to actually increment any id state so we must 150 // clone 151 152 if (_generatorMap.containsKey(result.toLowerCase())) { 153 154 try { 155 NameGenerator cg = (NameGenerator)g.clone(); 156 157 while (_generatorMap.containsKey(result.toLowerCase())) 158 result = cg.nextId(); 159 160 } catch (CloneNotSupportedException e) { 161 throw new ApplicationRuntimeException(e); 162 } 163 } 164 165 return result; 166 } 167 168 /** 169 * Clears the allocator, resetting it to freshly allocated state. 170 */ 171 172 public void clear() 173 { 174 _generatorMap.clear(); 175 } 176 }