Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
TimeBasedAlphanumericIdentifierGenerator |
|
| 3.142857142857143;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 | } |