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    }