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

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

The newest version!
/*
 * 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.tiogasolutions.dev.common.id;

import java.io.Serializable;
import java.time.*;
import java.util.*;
import org.tiogasolutions.dev.common.StringUtils;

/**
 * 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 */ /** * 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 long serialVersionUID = 1L; 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 ZoneId zoneId; private final ZonedDateTime 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(String idFormat, ZonedDateTime prefixBegin, String prefixChars, int prefixLifeInSeconds, int minPrefixLength, int suffixLength) { this.idFormat = idFormat; this.prefixBegin = prefixBegin; this.zoneId = prefixBegin.getZone(); this.prefixLifeInSeconds = prefixLifeInSeconds; this.prefixChars = prefixChars.toCharArray(); this.minPrefixLength = minPrefixLength; this.suffixLength = suffixLength; this.suffixMaxInt = Integer.valueOf(StringUtils.padRight("", suffixLength, '9')); this.currentPrefix = ""; } public TwoPartIdGenerator() { this("%s-%s", ZonedDateTime.of(2013, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")), "ABCDFGHJKLMNPQRSTVWXYZ", 20, 5, 7); } /** * 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)) { 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. 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 = Duration.between(prefixBegin, ZonedDateTime.now(zoneId)); // Safe to cast to int here as will never exceed MAX_INT. int newPrefixNumber = (int) duration.getSeconds() / 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 - 2024 Weber Informatics LLC | Privacy Policy