/* * ExperienceMod - Bukkit server plugin for modifying the experience system in Minecraft. * Copyright (C) 2012 Kristian S. Stangeland * * This program 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 2 of * the License, or (at your option) any later version. * * This program 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 this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.xp.lookup; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Function; import com.google.common.collect.Lists; public abstract class SearchTree<TKey, TValue> { /** * Contains an entry in the search tree. * * @author Kristian */ protected class SearchEntry { public TKey key; public TValue value; public int paramCount; // Simple constructor public SearchEntry(TKey key, TValue value) { this.key = key; this.value = value; } public SearchEntry(SearchEntry other) { this.key = other.key; this.value = other.value; this.paramCount = other.paramCount; } } protected Map<Integer, SearchEntry> flatten = new HashMap<Integer, SearchEntry>(); protected int currentID; protected ValueComparer comparer = new ValueComparer(); public Integer put(TKey element, TValue value) { Integer id = getNextID(); SearchEntry entry = new SearchEntry(element, value); // Save value flatten.put(id, entry); // Extract parameters entry.paramCount = putFromParameters(element, id); return id; } public void putAll(SearchTree<TKey, TValue> other) { int offset = currentID; int highest = offset; // Insert everything from flatten for (Integer id : other.flatten.keySet()) { SearchEntry entry = new SearchEntry(other.flatten.get(id)); flatten.put(id + offset, entry); highest = Math.max(highest, id + offset); } // Make sure the parameters are updated too putAllParameters(other, offset); currentID = highest + 1; } /** * Retrieves an element by a query. * @param element - the query to use. * @return The retrieved element. */ public TValue get(TKey element) { Integer id = getID(element); return get(id); } /** * Retrieves an element by ID. * @param id - the ID of the element to retrieve. * @return The retrieved element. */ public TValue get(Integer id) { if (id != null && flatten.containsKey(id)) return flatten.get(id).value; else return null; } /** * Retrieves the number of specified parameters by this element. * @param id - the element's ID. * @return The number of parameters. * @throws IllegalArgumentException - if no element by that ID can be found. */ public int getParamCount(Integer id) { SearchEntry entry = flatten.get(id); if (entry != null) return entry.paramCount; else throw new IllegalArgumentException("Cannot find ID " + id); } public List<TValue> getAllRanked(TKey element) { // YES! LINQ, only slightly more painful. return Lists.transform(getAllRankedID(element), new Function<Integer, TValue>() { public TValue apply(Integer id) { return get(id); } }); } /** * Retrieves every possible matching action in ID form. * @param element - the query to match with. * @return List of IDs. */ public List<Integer> getAllRankedID(TKey element) { Set<Integer> candidates = getFromParameters(element); List<Integer> indexes = new ArrayList<Integer>(candidates.size()); for (Integer candidate : candidates) { if (candidate != null) { indexes.add(candidate); } } // Sort list and return Collections.sort(indexes, comparer); return indexes; } protected Integer getID(TKey element) { Set<Integer> candidates = getFromParameters(element); // No result? Better return null. if (candidates == null || candidates.size() == 0) return null; // Return the most recent element return Collections.min(candidates, comparer); } public boolean containsKey(TKey element) { return getID(element) != null; } /** * Returns a list of every stored value in this search tree. * @return Every stored value. */ public Collection<TValue> getValues() { List<TValue> values = new ArrayList<TValue>(); // Simple enumeration for (SearchEntry entry : flatten.values()) { values.add(entry.value); } return values; } /** * Returns a list of every stored key in this search tree. * @return Every stored value. */ public Collection<TKey> getKeys() { List<TKey> values = new ArrayList<TKey>(); for (SearchEntry entry : flatten.values()) { values.add(entry.key); } return values; } protected abstract void putAllParameters(SearchTree<TKey, TValue> other, Integer offset); protected abstract Integer putFromParameters(TKey source, Integer id); protected abstract Set<Integer> getFromParameters(TKey source); private int getNextID() { return currentID++; } /** * Compares values (referenced by ID) by priority. The newest elements are put first. */ protected class ValueComparer implements Comparator<Integer> { @Override public int compare(Integer a, Integer b) { // Higher before lower return compareObjects(b, a, false); } // Taken from Apache Commons 2.6 (ObjectUtils.compare) public <T extends Comparable<T>> int compareObjects(T c1, T c2, boolean nullGreater) { if (c1 == c2) { return 0; } else if (c1 == null) { return (nullGreater ? 1 : -1); } else if (c2 == null) { return (nullGreater ? -1 : 1); } return c1.compareTo(c2); } } }