Coverage Report - org.apache.commons.id.serial.TimeBasedAlphanumericIdentifierGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
TimeBasedAlphanumericIdentifierGenerator
97%
59/61
88%
30/34
3.143
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.id.serial;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.util.Arrays;
 21  
 import java.util.Calendar;
 22  
 import java.util.TimeZone;
 23  
 
 24  
 import org.apache.commons.id.AbstractStringIdentifierGenerator;
 25  
 
 26  
 
 27  
 /**
 28  
  * <code>TimeBasedAlphanumericIdentifierGenerator</code> is an identifier generator that generates
 29  
  * an alphanumeric identifier in base 36 as a String object from the current UTC time and an
 30  
  * internal counter.
 31  
  * <p>
 32  
  * The generator guarantees that all generated ids have an increasing natural sort order (even if
 33  
  * the time internally has an overflow). The implementation additionally guarantees, that all
 34  
  * instances within the same process do generate unique ids. All generated ids have the same length
 35  
  * (padding with 0's on the left), which is determined by the maximum size of a long value and the
 36  
  * <code>postfixSize</code> parameter passed to the constructor.
 37  
  * </p>
 38  
  * <p>
 39  
  * Note: To ensure unique ids that are created within the same millisecond (or maximum time
 40  
  * resolution of the system), the implementation uses an internal counter. The maximum value of this
 41  
  * counter is determined by the <code>postfixSize</code> parameter i.e. the largest value that can
 42  
  * be represented in base 36. If the counter exceeds this value, an IllegalStateException is thrown.
 43  
  * </p>
 44  
  * <p>
 45  
  * Note: The uniqueness of the generated ids cannot be guaranteed if the system performs time shifts
 46  
  * of more than a second, that affect the running processes.
 47  
  * </p>
 48  
  * 
 49  
  * @author Commons-Id team
 50  
  * @version $Id: TimeBasedAlphanumericIdentifierGenerator.java 480488 2006-11-29 08:57:26Z bayard $
 51  
  */
 52  
 public class TimeBasedAlphanumericIdentifierGenerator extends AbstractStringIdentifierGenerator
 53  
         implements Serializable {
 54  
 
 55  
     /**
 56  
      * <code>serialVersionUID</code> is the serializable UID for the binary version of the class.
 57  
      */
 58  
     private static final long serialVersionUID = 20060116L;
 59  
     /**
 60  
      * <code>padding</code> an array of '0' for improved padding performance.
 61  
      */
 62  
     private static final char[] padding;
 63  
     static {
 64  1
         padding = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH];
 65  1
         Arrays.fill(padding, '0');
 66  
     }
 67  
     /**
 68  
      * <code>UTC</code> is the UTC {@link TimeZone} instance.
 69  
      */
 70  1
     private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
 71  
     /**
 72  
      * <code>last</code> is the marker, when the counter was resetted the last time.
 73  
      */
 74  1
     private static long last = 0;
 75  
     /**
 76  
      * <code>counter</code> counts the number of generated identifiers since the last reset.
 77  
      */
 78  1
     private static long counter = 0;
 79  
     /**
 80  
      * <code>postfixSize</code> size of the postfix, that contains the padded counter in base 36.
 81  
      */
 82  
     private final int postfixSize;
 83  
     private final long offset;
 84  
     
 85  
     /**
 86  
      * Construct a TimeBasedAlphanumericIdentifierGenerator with a defined size of the postfix and
 87  
      * an offset for the time value. The offset can be used to manipulate the representation of the
 88  
      * time value in the generated id. If a TimeBasedAlphanumericIdentifierGenerator is constructed
 89  
      * with an offset of the current number of milliseconds since 1st Jan 1970 the first generated
 90  
      * id in the same millisecond will have an id consisting of a sequence of '0' characters.
 91  
      * 
 92  
      * @param postfixSize the size of the postfix
 93  
      * @param offset the offset taken into account for the time value
 94  
      * @throws IllegalArgumentException if <code>postfixSize</code> is negative or exceeds the
 95  
      *             maximum size for representing {@link Long#MAX_VALUE} in base 36
 96  
      */
 97  36
     public TimeBasedAlphanumericIdentifierGenerator(final int postfixSize, final long offset) {
 98  36
         if (postfixSize < 0 || postfixSize > MAX_LONG_ALPHANUMERIC_VALUE_LENGTH) {
 99  2
             throw new IllegalArgumentException("Invalid size for postfix");
 100  
         }
 101  34
         this.postfixSize = postfixSize;
 102  34
         this.offset = offset;
 103  34
     }
 104  
 
 105  
     /**
 106  
      * Construct a TimeBasedAlphanumericIdentifierGenerator with a defined size of the postfix.
 107  
      * 
 108  
      * @param postfixSize the size of the postfix defining the maximum number of possible ids
 109  
      *            generated within the same millisecond (depending on the time resolution of the
 110  
      *            running system)
 111  
      * @throws IllegalArgumentException if <code>postfixSize</code> is negative or exceeds the
 112  
      *             maximum size for representing {@link Long#MAX_VALUE} in base 36
 113  
      */
 114  
     public TimeBasedAlphanumericIdentifierGenerator(final int postfixSize) {
 115  14
         this(postfixSize, 0);
 116  12
     }
 117  
 
 118  
     /**
 119  
      * Construct a TimeBasedAlphanumericIdentifierGenerator with a default size of the postfix of 3.
 120  
      */
 121  
     public TimeBasedAlphanumericIdentifierGenerator() {
 122  4
         this(3);
 123  4
     }
 124  
 
 125  
     public long maxLength() {
 126  14
         return MAX_LONG_ALPHANUMERIC_VALUE_LENGTH + postfixSize;
 127  
     }
 128  
 
 129  
     public long minLength() {
 130  7
         return maxLength();
 131  
     }
 132  
 
 133  
     public String nextStringIdentifier() {
 134  
         long now;
 135  11098
         synchronized (this) {
 136  11098
             now = Calendar.getInstance(UTC).getTime().getTime(); // JDK 1.3 compatibility
 137  11098
             final long diff = now - last;
 138  
             // external time correction of more than a second or overflow
 139  11098
             if (diff > 0 || diff < -1000) {
 140  21
                 last = now;
 141  21
                 counter = 0;
 142  21
             } else {
 143  11077
                 if (diff != 0) {
 144  0
                     now = last; // ignore time shift
 145  
                 }
 146  11077
                 ++counter;
 147  
             }
 148  11098
         }
 149  11098
         final String postfix = counter > 0
 150  
                                           ? Long.toString(counter, ALPHA_NUMERIC_CHARSET_SIZE) : "";
 151  11098
         if (postfix.length() > postfixSize) {
 152  1
             throw new IllegalStateException(
 153  
                     "The maximum number of identifiers in this millisecond has been reached");
 154  
         }
 155  
         // ensure, that no negative value is used and values stay increasing
 156  11097
         long base = now - offset;
 157  11097
         long value = base < 0 ? base + Long.MAX_VALUE + 1 : base;
 158  11097
         final String time = Long.toString(value, ALPHA_NUMERIC_CHARSET_SIZE);
 159  11097
         final char[] buffer = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH + postfixSize];
 160  11097
         int i = 0;
 161  11097
         int maxPad = MAX_LONG_ALPHANUMERIC_VALUE_LENGTH - time.length();
 162  11097
         if (maxPad > 0) {
 163  9510
             System.arraycopy(padding, 0, buffer, 0, maxPad);
 164  
         }
 165  11097
         System.arraycopy(time.toCharArray(), 0, buffer, maxPad, time.length());
 166  11097
         if (base < 0) {
 167  
             // Representation of Long.MAX_VALUE starts with '1', negative 'base' means higher value
 168  
             // in time
 169  6260
             buffer[0] += 2;
 170  
         }
 171  11097
         i += time.length() + maxPad;
 172  11097
         if (postfixSize > 0) {
 173  11094
             maxPad = postfixSize - postfix.length();
 174  11094
             if (maxPad > 0) {
 175  11093
                 System.arraycopy(padding, 0, buffer, i, maxPad);
 176  11093
                 i += maxPad;
 177  
             }
 178  11094
             System.arraycopy(postfix.toCharArray(), 0, buffer, i, postfix.length());
 179  
         }
 180  11097
         return new String(buffer);
 181  
     }
 182  
 
 183  
     /**
 184  
      * Retrieve the number of milliseconds since 1st Jan 1970 that were the base for the given id.
 185  
      * 
 186  
      * @param id the id to use
 187  
      * @param offset the offset used to create the id
 188  
      * @return the number of milliseconds
 189  
      * @throws IllegalArgumentException if <code>id</code> is not a valid id from this type of
 190  
      *             generator
 191  
      */
 192  
     public long getMillisecondsFromId(final Object id, final long offset) {
 193  2
         if (id instanceof String && id.toString().length() >= MAX_LONG_ALPHANUMERIC_VALUE_LENGTH) {
 194  2
             final char[] buffer = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH];
 195  2
             System.arraycopy(
 196  
                     id.toString().toCharArray(), 0, buffer, 0, MAX_LONG_ALPHANUMERIC_VALUE_LENGTH);
 197  2
             final boolean overflow = buffer[0] > '1';
 198  2
             if (overflow) {
 199  1
                 buffer[0] -= 2;
 200  
             }
 201  2
             long value = Long.parseLong(new String(buffer), ALPHA_NUMERIC_CHARSET_SIZE);
 202  2
             if (overflow) {
 203  1
                 value -= Long.MAX_VALUE + 1;
 204  
             }
 205  2
             return value + offset;
 206  
         }
 207  0
         throw new IllegalArgumentException("'" + id + "' is not an id from this generator");
 208  
     }
 209  
 }