    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.ioc.internal.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}