    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}