001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.ioc.util;
014
015import org.apache.tapestry5.commons.util.CollectionFactory;
016import org.apache.tapestry5.ioc.internal.util.InternalUtils;
017
018import java.util.HashMap;
019import java.util.IdentityHashMap;
020import java.util.List;
021import java.util.Map;
022
023/**
024 * Used to "uniquify" names within a given context. A base name is passed in, and the return value is the base name, or
025 * the base name extended with a suffix to make it unique.
026 *
027 * This class is not threadsafe.
028 */
029
030public final class IdAllocator
031{
032    private static final String SEPARATOR = "_";
033
034    /**
035     * Map from allocated id to a generator for names associated with the allocated id.
036     */
037    private final Map<String, NameGenerator> generatorMap;
038
039    private final String namespace;
040
041    /**
042     * Generates unique names with a particular prefix.
043     */
044    private static class NameGenerator implements Cloneable
045    {
046        private final String baseId;
047
048        private int index;
049
050        NameGenerator(String baseId)
051        {
052            this.baseId = baseId + SEPARATOR;
053        }
054
055        public String nextId()
056        {
057            return baseId + index++;
058        }
059
060        /**
061         * Clones this instance, returning an equivalent but separate copy.
062         */
063        @SuppressWarnings(
064        { "CloneDoesntDeclareCloneNotSupportedException" })
065        @Override
066        public NameGenerator clone()
067        {
068            try
069            {
070                return (NameGenerator) super.clone();
071            }
072            catch (CloneNotSupportedException ex)
073            {
074                // Unreachable!
075                throw new RuntimeException(ex);
076            }
077        }
078    }
079
080    /**
081     * Creates a new allocator with no namespace.
082     */
083    public IdAllocator()
084    {
085        this("");
086    }
087
088    /**
089     * Creates a new allocator with the provided namespace.
090     */
091    public IdAllocator(String namespace)
092    {
093        this(namespace, new HashMap<String, NameGenerator>());
094    }
095
096    private IdAllocator(String namespace, Map<String, NameGenerator> generatorMap)
097    {
098        this.namespace = namespace;
099        this.generatorMap = generatorMap;
100    }
101
102    /**
103     * Returns a list of all allocated ids, sorted alphabetically.
104     */
105    public List<String> getAllocatedIds()
106    {
107        return InternalUtils.sortedKeys(generatorMap);
108    }
109
110    /**
111     * Creates a clone of this IdAllocator instance, copying the allocator's namespace and key map.
112     */
113    @SuppressWarnings(
114    { "CloneDoesntCallSuperClone" })
115    @Override
116    public IdAllocator clone()
117    {
118        // Copying the generatorMap is tricky; multiple keys will point to the same NameGenerator
119        // instance. We need to clone the NameGenerators, then build a new map around the clones.
120
121        IdentityHashMap<NameGenerator, NameGenerator> transformMap = new IdentityHashMap<NameGenerator, NameGenerator>();
122
123        for (NameGenerator original : generatorMap.values())
124        {
125            NameGenerator copy = original.clone();
126
127            transformMap.put(original, copy);
128        }
129
130        Map<String, NameGenerator> mapCopy = CollectionFactory.newMap();
131
132        for (Map.Entry<String, NameGenerator> entry : generatorMap.entrySet())
133        {
134            NameGenerator copy = transformMap.get(entry.getValue());
135
136            mapCopy.put(entry.getKey(), copy);
137        }
138
139        return new IdAllocator(namespace, mapCopy);
140    }
141
142    /**
143     * Allocates the id. Repeated calls for the same name will return "name", "name_0", "name_1", etc.
144     */
145    public String allocateId(String name)
146    {
147        String key = name + namespace;
148
149        NameGenerator g = generatorMap.get(key);
150        String result;
151
152        if (g == null)
153        {
154            g = new NameGenerator(key);
155            result = key;
156        }
157        else
158            result = g.nextId();
159
160        // Handle the degenerate case, where a base name of the form "foo_0" has been
161        // requested. Skip over any duplicates thus formed.
162
163        while (generatorMap.containsKey(result))
164            result = g.nextId();
165
166        generatorMap.put(result, g);
167
168        return result;
169    }
170
171    /**
172     * Checks to see if a given name has been allocated.
173     */
174    public boolean isAllocated(String name)
175    {
176        return generatorMap.containsKey(name);
177    }
178
179    /**
180     * Clears the allocator, resetting it to freshly allocated state.
181     */
182    public void clear()
183    {
184        generatorMap.clear();
185    }
186}