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
015package org.apache.tapestry5.ioc.util;
016
017import java.util.HashMap;
018import java.util.IdentityHashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
023import 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
032public 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}