View Javadoc

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          padding = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH];
65          Arrays.fill(padding, '0');
66      }
67      /**
68       * <code>UTC</code> is the UTC {@link TimeZone} instance.
69       */
70      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      private static long last = 0;
75      /**
76       * <code>counter</code> counts the number of generated identifiers since the last reset.
77       */
78      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      public TimeBasedAlphanumericIdentifierGenerator(final int postfixSize, final long offset) {
98          if (postfixSize < 0 || postfixSize > MAX_LONG_ALPHANUMERIC_VALUE_LENGTH) {
99              throw new IllegalArgumentException("Invalid size for postfix");
100         }
101         this.postfixSize = postfixSize;
102         this.offset = offset;
103     }
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         this(postfixSize, 0);
116     }
117 
118     /**
119      * Construct a TimeBasedAlphanumericIdentifierGenerator with a default size of the postfix of 3.
120      */
121     public TimeBasedAlphanumericIdentifierGenerator() {
122         this(3);
123     }
124 
125     public long maxLength() {
126         return MAX_LONG_ALPHANUMERIC_VALUE_LENGTH + postfixSize;
127     }
128 
129     public long minLength() {
130         return maxLength();
131     }
132 
133     public String nextStringIdentifier() {
134         long now;
135         synchronized (this) {
136             now = Calendar.getInstance(UTC).getTime().getTime(); // JDK 1.3 compatibility
137             final long diff = now - last;
138             // external time correction of more than a second or overflow
139             if (diff > 0 || diff < -1000) {
140                 last = now;
141                 counter = 0;
142             } else {
143                 if (diff != 0) {
144                     now = last; // ignore time shift
145                 }
146                 ++counter;
147             }
148         }
149         final String postfix = counter > 0
150                                           ? Long.toString(counter, ALPHA_NUMERIC_CHARSET_SIZE) : "";
151         if (postfix.length() > postfixSize) {
152             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         long base = now - offset;
157         long value = base < 0 ? base + Long.MAX_VALUE + 1 : base;
158         final String time = Long.toString(value, ALPHA_NUMERIC_CHARSET_SIZE);
159         final char[] buffer = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH + postfixSize];
160         int i = 0;
161         int maxPad = MAX_LONG_ALPHANUMERIC_VALUE_LENGTH - time.length();
162         if (maxPad > 0) {
163             System.arraycopy(padding, 0, buffer, 0, maxPad);
164         }
165         System.arraycopy(time.toCharArray(), 0, buffer, maxPad, time.length());
166         if (base < 0) {
167             // Representation of Long.MAX_VALUE starts with '1', negative 'base' means higher value
168             // in time
169             buffer[0] += 2;
170         }
171         i += time.length() + maxPad;
172         if (postfixSize > 0) {
173             maxPad = postfixSize - postfix.length();
174             if (maxPad > 0) {
175                 System.arraycopy(padding, 0, buffer, i, maxPad);
176                 i += maxPad;
177             }
178             System.arraycopy(postfix.toCharArray(), 0, buffer, i, postfix.length());
179         }
180         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         if (id instanceof String && id.toString().length() >= MAX_LONG_ALPHANUMERIC_VALUE_LENGTH) {
194             final char[] buffer = new char[MAX_LONG_ALPHANUMERIC_VALUE_LENGTH];
195             System.arraycopy(
196                     id.toString().toCharArray(), 0, buffer, 0, MAX_LONG_ALPHANUMERIC_VALUE_LENGTH);
197             final boolean overflow = buffer[0] > '1';
198             if (overflow) {
199                 buffer[0] -= 2;
200             }
201             long value = Long.parseLong(new String(buffer), ALPHA_NUMERIC_CHARSET_SIZE);
202             if (overflow) {
203                 value -= Long.MAX_VALUE + 1;
204             }
205             return value + offset;
206         }
207         throw new IllegalArgumentException("'" + id + "' is not an id from this generator");
208     }
209 }