Coverage Report - org.apache.tapestry.util.IdAllocator
 
Classes in this File Line Coverage Branch Coverage Complexity
IdAllocator
0%
0/68
0%
0/22
2.294
IdAllocator$NameGenerator
0%
0/24
0%
0/12
2.294
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.util;
 16  
 
 17  
 import org.apache.hivemind.ApplicationRuntimeException;
 18  
 import org.apache.hivemind.util.Defense;
 19  
 import org.apache.tapestry.TapestryUtils;
 20  
 
 21  
 import java.util.ArrayList;
 22  
 import java.util.HashMap;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 
 26  
 /**
 27  
  * Used to "uniquify" names within a given context. A base name is passed in,
 28  
  * and the return value is the base name, or the base name extended with a
 29  
  * suffix to make it unique.
 30  
  *
 31  
  * @author Howard Lewis Ship
 32  
  * @since 3.0
 33  
  */
 34  
 
 35  
 public class IdAllocator
 36  
 {
 37  
 
 38  
     private static final String SEPARATOR = "_";
 39  
 
 40  
     private static final char NAME_SEPARATOR = '$';
 41  
 
 42  0
     final Map _generatorMap = new HashMap();
 43  0
     final List _uniqueGenerators = new ArrayList();
 44  
 
 45  
     final String _namespace;
 46  
 
 47  
     /** Class used only by IdAllocator. */
 48  0
     private class NameGenerator implements Cloneable
 49  
     {
 50  
         private final String _baseId;
 51  
 
 52  
         private int _index;
 53  
 
 54  
         NameGenerator(String baseId)
 55  0
         {
 56  0
             _baseId = baseId + SEPARATOR;
 57  0
         }
 58  
 
 59  
         NameGenerator(String baseId, int index)
 60  0
         {
 61  0
             _baseId = baseId + SEPARATOR;
 62  0
             _index = index;
 63  0
         }
 64  
 
 65  
         public String nextId()
 66  
         {
 67  0
             return _baseId + _index++;
 68  
         }
 69  
 
 70  
         public String peekId()
 71  
         {
 72  0
             return _baseId + _index;
 73  
         }
 74  
 
 75  
         public String getBaseId()
 76  
         {
 77  0
             return _baseId.substring(0, _baseId.length() - 1);
 78  
         }
 79  
 
 80  
         /**
 81  
          * {@inheritDoc}
 82  
          */
 83  
         protected Object clone()
 84  
                 throws CloneNotSupportedException
 85  
         {
 86  0
             return super.clone();
 87  
         }
 88  
 
 89  
         public boolean equals(Object o)
 90  
         {
 91  0
             if (this == o)
 92  0
                 return true;
 93  0
             if (!(o instanceof NameGenerator))
 94  0
                 return false;
 95  
 
 96  0
             NameGenerator that = (NameGenerator) o;
 97  
 
 98  0
             if (_baseId != null ? !_baseId.equals(that._baseId) : that._baseId != null)
 99  0
                 return false;
 100  
 
 101  0
             return true;
 102  
         }
 103  
 
 104  
         public int hashCode()
 105  
         {
 106  
             int result;
 107  0
             result = (_baseId != null ? _baseId.hashCode() : 0);
 108  0
             result = 31 * result + _index;
 109  0
             return result;
 110  
         }
 111  
 
 112  
         public String toString()
 113  
         {
 114  0
             return "NameGenerator[" +
 115  
                    "_baseId='" + _baseId + '\'' +
 116  
                    '\n' +
 117  
                    ", _index=" + _index +
 118  
                    '\n' +
 119  
                    ']';
 120  
         }
 121  
     }
 122  
 
 123  
     public IdAllocator()
 124  
     {
 125  0
         this("");
 126  0
     }
 127  
 
 128  
     public IdAllocator(String namespace)
 129  0
     {
 130  0
         Defense.notNull(namespace, "namespace");
 131  
 
 132  0
         _namespace = namespace;
 133  0
     }
 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  0
         String key = name + _namespace;
 148  
 
 149  0
         NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase());
 150  0
         String result = null;
 151  
 
 152  0
         if (g == null)
 153  
         {
 154  0
             g = new NameGenerator(key);
 155  0
             _uniqueGenerators.add(g);
 156  0
             result = key;
 157  
         }
 158  
         else
 159  0
             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  0
         while(_generatorMap.containsKey(result.toLowerCase()))
 166  0
             result = g.nextId();
 167  
 
 168  0
         _generatorMap.put(result.toLowerCase(), g);
 169  
 
 170  0
         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  0
         String key = name + _namespace;
 184  
 
 185  0
         NameGenerator g = (NameGenerator) _generatorMap.get(key.toLowerCase());
 186  0
         String result = null;
 187  
 
 188  0
         if (g == null)
 189  
         {
 190  0
             g = new NameGenerator(key);
 191  0
             result = key;
 192  
         } else
 193  0
             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  0
         if (_generatorMap.containsKey(result.toLowerCase()))
 203  
         {
 204  
             try {
 205  0
                 NameGenerator cg = (NameGenerator)g.clone();
 206  
 
 207  0
                 while (_generatorMap.containsKey(result.toLowerCase()))
 208  0
                     result = cg.nextId();
 209  
 
 210  0
             } catch (CloneNotSupportedException e) {
 211  0
                 throw new ApplicationRuntimeException(e);
 212  0
             }
 213  
         }
 214  
 
 215  0
         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  0
         StringBuffer str = new StringBuffer(_namespace);
 227  
 
 228  0
         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  0
             str.append(",");
 233  
 
 234  0
             NameGenerator g = (NameGenerator) _uniqueGenerators.get(i);
 235  
 
 236  0
             str.append(g.getBaseId()).append(NAME_SEPARATOR);
 237  0
             str.append(g._index);
 238  
         }
 239  
 
 240  0
         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  0
         NameGenerator g = new NameGenerator(baseId, 0);
 253  0
         _uniqueGenerators.add(g);
 254  0
         _generatorMap.put(baseId.toLowerCase(), g);
 255  
         
 256  
         // add generated key to map until we reach top level index value
 257  0
         while(g._index != index)
 258  
         {
 259  0
             String nextId = g.nextId().toLowerCase();
 260  
 
 261  
             // only dump value in if not already covered by another allocator
 262  
             
 263  0
             if (!_generatorMap.containsKey(nextId))
 264  
             {
 265  0
                 _generatorMap.put(nextId, g);
 266  
             }
 267  0
         }
 268  0
     }
 269  
 
 270  
     /**
 271  
      * Clears the allocator, resetting it to freshly allocated state.
 272  
      */
 273  
 
 274  
     public void clear()
 275  
     {
 276  0
         _generatorMap.clear();
 277  0
         _uniqueGenerators.clear();
 278  0
     }
 279  
 
 280  
     public static IdAllocator fromExternalString(String seed)
 281  
     {
 282  0
         Defense.notNull(seed, "seed");
 283  
 
 284  0
         String[] values = TapestryUtils.split(seed);
 285  0
         if (values.length == 0)
 286  
         {
 287  0
             return new IdAllocator();
 288  
         }
 289  
 
 290  0
         String namespace = values[0];
 291  
 
 292  0
         IdAllocator idAllocator = new IdAllocator(namespace);
 293  
 
 294  0
         for (int i=1; i < values.length; i++)
 295  
         {
 296  0
             int index = values[i].lastIndexOf(NAME_SEPARATOR);
 297  
 
 298  0
             if (index < 0)
 299  0
                 continue;
 300  
 
 301  0
             String baseId = values[i].substring(0, index);
 302  0
             int valueIndex = Integer.parseInt(values[i].substring(index + 1, values[i].length()));
 303  
 
 304  0
             idAllocator.addSeed(baseId, valueIndex);
 305  
         }
 306  
 
 307  0
         return idAllocator;
 308  
     }
 309  
 }