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 * Utility for stripping out the standard allocator generated portion of a component id string 088 * in order to get what the most likely original component id was. 089 * 090 * @param input 091 * The generated component id. 092 * @return The id stripped of any allocated id meta, if any was found. 093 */ 094 public static String convertAllocatedComponentId(String input) 095 { 096 if (input == null) 097 return null; 098 099 int index = input.indexOf(SEPARATOR); 100 101 return index > -1 ? input.substring(0, index) : input; 102 } 103 104 /** 105 * Allocates the id. Repeated calls for the same name will return "name", 106 * "name_0", "name_1", etc. 107 */ 108 109 public String allocateId(String name) 110 { 111 String key = name + _namespace; 112 113 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 114 String result = null; 115 116 if (g == null) 117 { 118 g = new NameGenerator(key); 119 result = key; 120 } 121 else 122 result = g.nextId(); 123 124 // Handle the degenerate case, where a base name of the form "foo$0" has 125 // been 126 // requested. Skip over any duplicates thus formed. 127 128 while(_generatorMap.containsKey(result.toLowerCase())) 129 result = g.nextId(); 130 131 _generatorMap.put(result.toLowerCase(), g); 132 133 return result; 134 } 135 136 /** 137 * Should return the exact same thing as {@link #allocateId(String)}, with the difference 138 * that the calculated id is not allocated and stored so multiple calls will always return the 139 * same thing. 140 * 141 * @param name The name to peek at. 142 * @return The next id that will be allocated for the given name. 143 */ 144 public String peekNextId(String name) 145 { 146 String key = name + _namespace; 147 148 NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase()); 149 String result = null; 150 151 if (g == null) 152 { 153 g = new NameGenerator(key); 154 result = key; 155 } else 156 result = g.peekId(); 157 158 // Handle the degenerate case, where a base name of the form "foo_0" has 159 // been 160 // requested. Skip over any duplicates thus formed. 161 162 // in a peek we don't want to actually increment any id state so we must 163 // clone 164 165 if (_generatorMap.containsKey(result.toLowerCase())) { 166 167 try { 168 NameGenerator cg = (NameGenerator)g.clone(); 169 170 while (_generatorMap.containsKey(result.toLowerCase())) 171 result = cg.nextId(); 172 173 } catch (CloneNotSupportedException e) { 174 throw new ApplicationRuntimeException(e); 175 } 176 } 177 178 return result; 179 } 180 181 /** 182 * Clears the allocator, resetting it to freshly allocated state. 183 */ 184 185 public void clear() 186 { 187 _generatorMap.clear(); 188 } 189 }