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    }