/* * This file is part of aion-emu <aion-emu.com>. * * aion-emu is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * aion-emu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with aion-emu. If not, see <http://www.gnu.org/licenses/>. */ package com.aionemu.gameserver.utils.idfactory; import java.util.BitSet; import java.util.concurrent.locks.ReentrantLock; /** * This class is responsible for id generation for all Aion-Emu objects.<br> * This class is Thread-Safe.<br> * This class is designed to be very strict with id usage. Any illegal operation will throw {@link IDFactoryError} * * @author SoulKeeper */ public class IDFactory { /** * Bitset that is used for all id's.<br> * We are allowing BitSet to grow over time, so in the end it can be as big as {@link Integer#MAX_VALUE} */ private final BitSet idList = new BitSet(); /** * Synchronization of bitset */ private final ReentrantLock lock = new ReentrantLock(); /** * Id that will be used as minimal on next id request */ private int nextMinId = 0; /** * Returns next free id. * * @return next free id * @throws IDFactoryError * if there is no free id's */ public int nextId() { try { lock.lock(); int id; if(nextMinId == Integer.MIN_VALUE) { // Error will be thrown few lines later, we have no more free id's. // BitSet will throw IllegalArgumentException if nextMinId is negative id = Integer.MIN_VALUE; } else { id = idList.nextClearBit(nextMinId); } // If BitSet reached Integer.MAX_VALUE size and returned last free id before - it will return // Intger.MIN_VALUE as the next id, so we must catch such case and throw error (no free id's left) if(id == Integer.MIN_VALUE) { throw new IDFactoryError("All id's are used, please clear your database"); } idList.set(id); // It ok to have Integer OverFlow here, on next ID request IDFactory will throw error nextMinId = id + 1; return id; } finally { lock.unlock(); } } /** * Locks given ids. * * @param ids * ids to lock * @throws IDFactoryError * if some of the id's were locked before */ public void lockIds(int... ids) { try { lock.lock(); for(int id : ids) { boolean status = idList.get(id); if(status) { throw new IDFactoryError("ID " + id + " is already taken, fatal error!!!"); } idList.set(id); } } finally { lock.unlock(); } } /** * Locks given ids. * * @param ids * ids to lock * @throws IDFactoryError * if some of the id's were locked before */ public void lockIds(Iterable<Integer> ids) { try { lock.lock(); for(int id : ids) { boolean status = idList.get(id); if(status) { throw new IDFactoryError("ID " + id + " is already taken, fatal error!!!"); } idList.set(id); } } finally { lock.unlock(); } } /** * Releases given id * * @param id * id to release * @throws IDFactoryError * if id was not taken earlier */ public void releaseId(int id) { try { lock.lock(); boolean status = idList.get(id); if(!status) { throw new IDFactoryError("ID " + id + " is not taken, can't release it."); } idList.clear(id); if(id < nextMinId || nextMinId == Integer.MIN_VALUE) { nextMinId = id; } } finally { lock.unlock(); } } /** * Returns amount of used ids * * @return amount of used ids */ public int getUsedCount() { try { lock.lock(); return idList.cardinality(); } finally { lock.unlock(); } } }