    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    }