All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.crazyyak.dev.common.id.TwoPartIdGenerator Maven / Gradle / Ivy

/*
 * Copyright 2014 Harlan Noonkester
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.crazyyak.dev.common.id;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.crazyyak.dev.common.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;

import java.io.Serializable;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

/**
 * User: harlan
 * Date: 3/20/12
 * Time: 12:00 AM
 * 

* Considerations - * prefixLifeInSeconds should not be greater than the time it takes to reset the server. * REVIEW - consider having the generator ensure it has existed at a minimum of prefixLifeInSeconds before generating the first sequence. * REVIEW - concurrency */ public class TwoPartIdGenerator implements IdGenerator, Serializable { private static final Log log = LogFactory.getLog(TwoPartIdGenerator.class); private static final int MAX_ATTEMPTS = 10000000; // 10 million private final Object idLock = new Object(); private final Set rememberedRandomSet = new TreeSet(); private final Random random = new Random(System.currentTimeMillis()); private final String idFormat; private final DateTime prefixBegin; private final char[] prefixChars; private final int prefixLifeInSeconds; private final int minPrefixLength; private final int suffixLength; private final int suffixMaxInt; private String currentPrefix; private int currentPrefixNumber; private String lastId; public TwoPartIdGenerator() { this("%s-%s", new DateTime(2013, 1, 1, 0, 0, 0, DateTimeZone.UTC), "ABCDFGHJKLMNPQRSTVWXYZ", 20, 5, 7); } public TwoPartIdGenerator(String idFormat, DateTime prefixBegin, String prefixChars, int prefixLifeInSeconds, int minPrefixLength, int suffixLength) { this.idFormat = idFormat; this.prefixBegin = prefixBegin; this.prefixLifeInSeconds = prefixLifeInSeconds; this.prefixChars = prefixChars.toCharArray(); this.minPrefixLength = minPrefixLength; this.suffixLength = suffixLength; this.suffixMaxInt = Integer.valueOf(StringUtils.padRight("", suffixLength, '9')); this.currentPrefix = ""; } /** * Generate a new sequence. * * @return generated sequence */ // CRITICAL should this method be synchronized since we use idLock @Override public synchronized String newId() { // We perform this in a loop so that if we find a duplicate we can regenerate the entire number. int attempts = 0; while (attempts++ < MAX_ATTEMPTS) { // Generate the prefix. String lastPrefix = currentPrefix; String prefix = generatePrefix(); // If our prefix has changed we need to clear the rememberedRandomSet if (!lastPrefix.equals(prefix)) { if (log.isDebugEnabled()) { log.debug("Random set cleared at " + currentPrefix + ", size is: " + rememberedRandomSet.size()); } rememberedRandomSet.clear(); } // Create the suffixInt and ensure it's unique. int suffixInt = random.nextInt(suffixMaxInt); if (rememberedRandomSet.contains(suffixInt)) { // This is not a unique number, generate another. if (log.isDebugEnabled()) { log.debug("We found duplicate at " + currentPrefix + ". Set size is " + rememberedRandomSet.size()); } continue; } // We have a unique randomInt, so generate the suffix which needs to be padded to suffixLength. rememberedRandomSet.add(suffixInt); String suffix = StringUtils.padRight(String.valueOf(suffixInt), suffixLength, '0'); // Return the sequence. String sequence = String.format(idFormat, prefix, suffix); synchronized (idLock) { lastId = sequence; } return sequence; } // We reached max attempts but never successfully generated sequence, throw exception. throw new RuntimeException("Exceeded number of attempts at generating a sequence."); } public String getLastId() { synchronized (idLock) { return lastId; } } private String generatePrefix() { // Duration will be used to generate the prefix characters, which are time based. Duration duration = new Duration(prefixBegin, new DateTime()); // Safe to cast to int here as will never exceed MAX_INT. int newPrefixNumber = (int) duration.getStandardSeconds() / prefixLifeInSeconds; if (newPrefixNumber == currentPrefixNumber) { // Our prefix number has not changed so just return the previous prefix. return currentPrefix; } // We have a new currentPrefixNumber; currentPrefixNumber = newPrefixNumber; // Construct prefix, which is representation of prefixNumber based on the prefix chars. int base = prefixChars.length; StringBuilder prefixSb = new StringBuilder(); while (newPrefixNumber != 0) { int mod = newPrefixNumber % base; prefixSb.insert(0, prefixChars[mod]); newPrefixNumber = newPrefixNumber / base; } currentPrefix = StringUtils.padLeft(prefixSb.toString(), minPrefixLength, this.prefixChars[0]); return currentPrefix; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy