001    // Copyright 2004, 2005 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.tapestry.util;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.hivemind.util.Defense;
019    import org.apache.tapestry.TapestryUtils;
020    
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    /**
027     * Used to "uniquify" names within a given context. A base name is passed in,
028     * and the return value is the base name, or the base name extended with a
029     * suffix to make it unique.
030     *
031     * @author Howard Lewis Ship
032     * @since 3.0
033     */
034    
035    public class IdAllocator
036    {
037    
038        private static final String SEPARATOR = "_";
039    
040        private static final char NAME_SEPARATOR = '$';
041    
042        final Map _generatorMap = new HashMap();
043        final List _uniqueGenerators = new ArrayList();
044    
045        final String _namespace;
046    
047        /** Class used only by IdAllocator. */
048        private class NameGenerator implements Cloneable
049        {
050            private final String _baseId;
051    
052            private int _index;
053    
054            NameGenerator(String baseId)
055            {
056                _baseId = baseId + SEPARATOR;
057            }
058    
059            NameGenerator(String baseId, int index)
060            {
061                _baseId = baseId + SEPARATOR;
062                _index = index;
063            }
064    
065            public String nextId()
066            {
067                return _baseId + _index++;
068            }
069    
070            public String peekId()
071            {
072                return _baseId + _index;
073            }
074    
075            public String getBaseId()
076            {
077                return _baseId.substring(0, _baseId.length() - 1);
078            }
079    
080            /**
081             * {@inheritDoc}
082             */
083            protected Object clone()
084                    throws CloneNotSupportedException
085            {
086                return super.clone();
087            }
088    
089            public boolean equals(Object o)
090            {
091                if (this == o)
092                    return true;
093                if (!(o instanceof NameGenerator))
094                    return false;
095    
096                NameGenerator that = (NameGenerator) o;
097    
098                if (_baseId != null ? !_baseId.equals(that._baseId) : that._baseId != null)
099                    return false;
100    
101                return true;
102            }
103    
104            public int hashCode()
105            {
106                int result;
107                result = (_baseId != null ? _baseId.hashCode() : 0);
108                result = 31 * result + _index;
109                return result;
110            }
111    
112            public String toString()
113            {
114                return "NameGenerator[" +
115                       "_baseId='" + _baseId + '\'' +
116                       '\n' +
117                       ", _index=" + _index +
118                       '\n' +
119                       ']';
120            }
121        }
122    
123        public IdAllocator()
124        {
125            this("");
126        }
127    
128        public IdAllocator(String namespace)
129        {
130            Defense.notNull(namespace, "namespace");
131    
132            _namespace = namespace;
133        }
134    
135        /**
136         * Allocates the id. Repeated calls for the same name will return "name",
137         * "name_0", "name_1", etc.
138         *
139         * @param name
140         *          The base id to allocate new unique ids from.
141         *
142         * @return A unique version of the passed in id.
143         */
144    
145        public String allocateId(String name)
146        {
147            String key = name + _namespace;
148    
149            NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase());
150            String result = null;
151    
152            if (g == null)
153            {
154                g = new NameGenerator(key);
155                _uniqueGenerators.add(g);
156                result = key;
157            }
158            else
159                result = g.nextId();
160    
161            // Handle the degenerate case, where a base name of the form "foo$0" has
162            // been
163            // requested. Skip over any duplicates thus formed.
164    
165            while(_generatorMap.containsKey(result.toLowerCase()))
166                result = g.nextId();
167    
168            _generatorMap.put(result.toLowerCase(), g);
169    
170            return result;
171        }
172    
173        /**
174         * Should return the exact same thing as {@link #allocateId(String)}, with the difference
175         * that the calculated id is not allocated and stored so multiple calls will always return the 
176         * same thing. 
177         *
178         * @param name The name to peek at.
179         * @return The next id that will be allocated for the given name.
180         */
181        public String peekNextId(String name)
182        {
183            String key = name + _namespace;
184    
185            NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase());
186            String result = null;
187    
188            if (g == null)
189            {
190                g = new NameGenerator(key);
191                result = key;
192            } else
193                result = g.peekId();
194    
195            // Handle the degenerate case, where a base name of the form "foo_0" has
196            // been
197            // requested. Skip over any duplicates thus formed.
198    
199            // in a peek we don't want to actually increment any id state so we must
200            // clone
201    
202            if (_generatorMap.containsKey(result.toLowerCase()))
203            {
204                try {
205                    NameGenerator cg = (NameGenerator)g.clone();
206    
207                    while (_generatorMap.containsKey(result.toLowerCase()))
208                        result = cg.nextId();
209    
210                } catch (CloneNotSupportedException e) {
211                    throw new ApplicationRuntimeException(e);
212                }
213            }
214    
215            return result;
216        }
217    
218        /**
219         * Creates a custom string representation of the current state of this instance, capable
220         * of being re-created by using the corresponding {@link IdAllocator#fromExternalString(String)} method.
221         *
222         * @return The external string representation of the current state of this instance.
223         */
224        public String toExternalString()
225        {
226            StringBuffer str = new StringBuffer(_namespace);
227    
228            for (int i=0; i < _uniqueGenerators.size(); i++)
229            {
230                // namespace is always the first element, so safe to always add comma here
231    
232                str.append(",");
233    
234                NameGenerator g = (NameGenerator) _uniqueGenerators.get(i);
235    
236                str.append(g.getBaseId()).append(NAME_SEPARATOR);
237                str.append(g._index);
238            }
239    
240            return str.toString();
241        }
242    
243        /**
244         * Using the base id and index value,  re-creates the state of generated id values in the
245         * internal map to what it would have been when generating ids orginally.
246         *
247         * @param baseId The base id being seeded.
248         * @param index The last known index value used for the id.
249         */
250        void addSeed(String baseId, int index)
251        {
252            NameGenerator g = new NameGenerator(baseId, 0);
253            _uniqueGenerators.add(g);
254            _generatorMap.put(baseId.toLowerCase(), g);
255            
256            // add generated key to map until we reach top level index value
257            while(g._index != index)
258            {
259                String nextId = g.nextId().toLowerCase();
260    
261                // only dump value in if not already covered by another allocator
262                
263                if (!_generatorMap.containsKey(nextId))
264                {
265                    _generatorMap.put(nextId, g);
266                }
267            }
268        }
269    
270        /**
271         * Clears the allocator, resetting it to freshly allocated state.
272         */
273    
274        public void clear()
275        {
276            _generatorMap.clear();
277            _uniqueGenerators.clear();
278        }
279    
280        public static IdAllocator fromExternalString(String seed)
281        {
282            Defense.notNull(seed, "seed");
283    
284            String[] values = TapestryUtils.split(seed);
285            if (values.length == 0)
286            {
287                return new IdAllocator();
288            }
289    
290            String namespace = values[0];
291    
292            IdAllocator idAllocator = new IdAllocator(namespace);
293    
294            for (int i=1; i < values.length; i++)
295            {
296                int index = values[i].lastIndexOf(NAME_SEPARATOR);
297    
298                if (index < 0)
299                    continue;
300    
301                String baseId = values[i].substring(0, index);
302                int valueIndex = Integer.parseInt(values[i].substring(index + 1, values[i].length()));
303    
304                idAllocator.addSeed(baseId, valueIndex);
305            }
306    
307            return idAllocator;
308        }
309    }