/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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.apache.geode.internal; import org.apache.geode.internal.i18n.LocalizedStrings; /** * UniqueIdGenerator is factory that will produce unique ids that fall in a range between 0 and * numIds-1 inclusive. Obtained ids will not be reissued until they are released and until every * other non-obtained id has been obtained first. * <p> * Memory Use: this implementation allocates numIds/64 8-byte longs at construction time and keeps * them allocated. * <p> * Instances of this class are thread safe. * * @since GemFire 5.0.2 * */ public class UniqueIdGenerator { /* * BitSets are packed into arrays of "units." Currently a unit is a long, which consists of 64 * bits, requiring 6 address bits. The choice of unit is determined purely by performance * concerns. */ private final static int ADDRESS_BITS_PER_UNIT = 6; private final static int BITS_PER_UNIT = 1 << ADDRESS_BITS_PER_UNIT; private final static int BIT_INDEX_MASK = BITS_PER_UNIT - 1; /** * The units in this BitSet. The ith bit is stored in units[i/64] at bit position i % 64 (where * bit position 0 refers to the least significant bit and 63 refers to the most significant bit). */ private final long units[]; /** * The maximum id supported by this generator */ private final int MAX_ID; /** * The next id to return from obtain if it is available. */ private int ctr = 0; /** * Given a bit index return unit index containing it. */ private static int unitIndex(int bitIndex) { return bitIndex >> ADDRESS_BITS_PER_UNIT; } /** * Given a bit index, return a unit that masks that bit in its unit. */ private static long bit(int bitIndex) { return 1L << (bitIndex & BIT_INDEX_MASK); } /** * Sets the bit at the specified index to <code>true</code> if it is not already in the set. * Otherwise does nothing. * * @param bitIndex a bit index. * @exception IndexOutOfBoundsException if the specified index is negative. */ private void setBit(int bitIndex) { int unitIndex = unitIndex(bitIndex); long bit = bit(bitIndex); this.units[unitIndex] |= bit; } /** * Sets the bit specified by the index to <code>false</code>. * * @param bitIndex the index of the bit to be cleared. */ private void clearBit(int bitIndex) { int unitIndex = unitIndex(bitIndex); long bit = bit(bitIndex); this.units[unitIndex] &= ~bit; } /* * trailingZeroTable[i] is the number of trailing zero bits in the binary representation of i. */ private final static byte trailingZeroTable[] = {-25, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; private static int trailingZeroCnt(long val) { // Loop unrolled for performance int byteVal = (int) val & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal]; } byteVal = (int) (val >>> 8) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 8; } byteVal = (int) (val >>> 16) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 16; } byteVal = (int) (val >>> 24) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 24; } byteVal = (int) (val >>> 32) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 32; } byteVal = (int) (val >>> 40) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 40; } byteVal = (int) (val >>> 48) & 0xff; if (byteVal != 0) { return trailingZeroTable[byteVal] + 48; } byteVal = (int) (val >>> 56) & 0xff; return trailingZeroTable[byteVal] + 56; } private static final long WORD_MASK = 0xffffffffffffffffL; /** * Returns the index of the first bit that is set to <code>false</code> that occurs on or after * the specified starting index. * * @param fromIndex the index to start checking from (inclusive). * @return the index of the next clear bit. */ private int nextClearBit(int fromIndex) { int u = unitIndex(fromIndex); int testIndex = (fromIndex & BIT_INDEX_MASK); long unit = this.units[u] >> testIndex; if (unit == (WORD_MASK >> testIndex)) { testIndex = 0; } while ((unit == WORD_MASK) && (u < this.units.length - 1)) { unit = this.units[++u]; } if (unit == WORD_MASK) { return -1; // could not find a clear bit } int result; if (unit == 0) { result = u * BITS_PER_UNIT + testIndex; } else { testIndex += trailingZeroCnt(~unit); result = ((u * BITS_PER_UNIT) + testIndex); } if (result > MAX_ID || result < 0) { return -1; } else { return result; } } /** * Creates unique id pool that has numIds in it. * * @param numIds the maximum number of ids */ public UniqueIdGenerator(int numIds) { if (numIds <= 0) { throw new IllegalArgumentException( LocalizedStrings.UniqueIdGenerator_NUMIDS_0.toLocalizedString()); } this.units = new long[(unitIndex(numIds - 1) + 1)]; this.MAX_ID = numIds - 1; this.ctr = 0; } /** * Obtain an id form the pool of available ids and return it. * * @throws IllegalStateException if all ids have been obtained */ public int obtain() { synchronized (this) { int candidate = this.ctr; int result = nextClearBit(candidate); if (result == -1 && candidate != 0) { // The following check will do some additional work by scanning // the range [candidate..MAX_ID] again if it does not find a clear bit // in the range [0..candidate-1] but it will only do this when // we are going to throw an exception. result = nextClearBit(0); } if (result == -1) { throw new IllegalStateException( LocalizedStrings.UniqueIdGenerator_RAN_OUT_OF_MESSAGE_IDS.toLocalizedString()); } else { setBit(result); if (result == MAX_ID) { this.ctr = 0; } else { this.ctr = result + 1; } return result; } } } /** * Release a previously obtained id allowing it to be obtained in the future. */ public void release(int id) { if (id < 0) { throw new IllegalArgumentException( LocalizedStrings.UniqueIdGenerator_NEGATIVE_ID_0.toLocalizedString(Integer.valueOf(id))); } else if (id > this.MAX_ID) { throw new IllegalArgumentException( LocalizedStrings.UniqueIdGenerator_ID_MAX_ID_0.toLocalizedString(Integer.valueOf(id))); } synchronized (this) { clearBit(id); } } }