/*
* This file is part of SpoutcraftPlugin.
*
* Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org//>
* SpoutcraftPlugin is licensed under the GNU Lesser General Public License.
*
* SpoutcraftPlugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpoutcraftPlugin 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.getspout.spoutapi.inventory;
import org.getspout.spoutapi.io.store.SimpleStore;
/**
* This is a map which maps custom item ids to Strings and back.
*
* This map can be backed by File to ensure persistence.
*
* It also provides functionality to convert ids between 2 maps.
*/
public class ItemMap {
private static ItemMap root;
private final ItemMap parent;
private final SimpleStore<Integer> store;
private final ItemMapRunnable updateTask;
private final int[] thisToParentMap;
private final int[] parentToThisMap;
private int nextId = 1024;
public ItemMap(ItemMap parent, SimpleStore<Integer> store, ItemMapRunnable updateTask) {
this.parent = parent;
this.store = store;
this.updateTask = updateTask;
thisToParentMap = new int[65536];
parentToThisMap = new int[65536];
for (int i = 0; i < 65536; i++) {
thisToParentMap[i] = 0;
parentToThisMap[i] = 0;
}
}
public static void setRootMap(ItemMap root) {
ItemMap.root = root;
}
public static ItemMap getRootMap() {
return root;
}
/**
* Converts an id local to this map to a foreign id, local to another map.
*
* @param other the other map
* @return returns the foreign id, or 0 on failure
*/
public int convertTo(ItemMap other, int localId) {
int foreignId = 0;
// Check cache
if (other == this) {
if (store.reverseGet(localId) != null) {
return localId;
} else {
return 0;
}
} else if (other == parent) {
foreignId = thisToParentMap[localId];
} else if (other.parent == this) {
foreignId = other.parentToThisMap[localId];
}
// Cache hit
if (foreignId != 0) {
return foreignId;
}
String localKey = store.reverseGet(localId);
// There is no entry in the local map to perform the translation
if (localKey == null) {
return 0;
}
Integer integerForeignId = other.store.get(localKey);
// The other map doesn't have an entry for this key
if (integerForeignId == null) {
integerForeignId = other.register(localKey);
}
// Add the key/value pair to the cache, if is no problem with the foreign key
if (integerForeignId != 0) {
if (other == parent) {
thisToParentMap[localId] = integerForeignId;
parentToThisMap[integerForeignId] = localId;
} else if (other.parent == this) {
other.thisToParentMap[integerForeignId] = localId;
other.parentToThisMap[localId] = integerForeignId;
}
}
return integerForeignId;
}
/**
* Converts a foreign id, local to a foreign map to an id local to this map.
*
* @param other the other map
* @return returns the local id, or 0 on failure
*/
public int convertFrom(ItemMap other, int foreignId) {
return other.convertTo(this, foreignId);
}
/**
* Registers a key with the map and returns the matching id.
*
* The id corresponding to a key will be consistent if registered more than once, including over restarts, subject to the persistence of the store.
*
* @param key the key to be added
* @return returns the local id, or 0 on failure
*/
public int register(String key) {
Integer id = store.get(key);
if (id != null) {
return id;
} else {
id = findFreeId();
if (id != 0) {
store.set(key, id);
if (updateTask != null) {
updateTask.run(this, key, id);
}
}
return id;
}
}
/**
* Saves the map to the persistence system
*
* @return returns true if the map saves correctly
*/
public boolean save() {
return store.save();
}
private int findFreeId() {
int offset = 0;
boolean freeFound = false;
int checkPos = 0;
while (offset < 65536 && !freeFound) {
checkPos = (offset + nextId) % 65536;
if (checkPos >= 1024 && store.reverseGet(checkPos) == null) {
freeFound = true;
}
offset++;
}
if (!freeFound) {
return 0;
} else {
nextId = checkPos + 1;
return checkPos;
}
}
public void rename(String oldKey, String newKey) {
Integer id = store.get(oldKey);
if (id != null) {
store.set(newKey, id);
store.remove(oldKey);
save();
}
}
/**
* Returns the id associated with the key.
* @param key The key to lookup
* @return The id associated with the key
*/
public Integer get(String key) {
return store.get(key);
}
}