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    }