Chat / Merge blocks placer / generator

This commit is contained in:
Jesse Boyd
2016-06-13 14:47:50 +10:00
parent 506455ae40
commit 4f0ede646e
71 changed files with 4523 additions and 4959 deletions

View File

@ -9,93 +9,103 @@ import java.util.Collection;
/**
* Represents a wrapper around an array class of an arbitrary reference type,
* which properly implements "value" hash code and equality functions.
* <p>
* This class is intended for use as a key to a map.
* </p>
*
* <p>This class is intended for use as a key to a map.
*
* @author Glen Husman
* @param <E> The type of elements in the array.
* @author Glen Husman
* @see Arrays
*/
public final class ArrayWrapper<E> {
private E[] array;
/**
* Creates an array wrapper with some elements.
*
* @param elements The elements of the array.
*/
public ArrayWrapper(E... elements) {
setArray(elements);
}
/**
* Creates an array wrapper with some elements.
* @param elements The elements of the array.
*/
public ArrayWrapper(E... elements) {
setArray(elements);
}
private E[] _array;
/**
* Converts an iterable element collection to an array of elements.
* The iteration order of the specified object will be used as the array element order.
* @param list The iterable of objects which will be converted to an array.
* @param c The type of the elements of the array.
* @return An array of elements in the specified iterable.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Iterable<? extends T> list, Class<T> c) {
int size = -1;
if (list instanceof Collection<?>) {
@SuppressWarnings("rawtypes") Collection coll = (Collection) list;
size = coll.size();
}
/**
* Retrieves a reference to the wrapped array instance.
*
* @return The array wrapped by this instance.
*/
public E[] getArray() {
return _array;
}
if (size < 0) {
size = 0;
// Ugly hack: Count it ourselves
for (@SuppressWarnings("unused") T element : list) {
size++;
}
}
/**
* Set this wrapper to wrap a new array instance.
*
* @param array The new wrapped array.
*/
public void setArray(E[] array) {
Validate.notNull(array, "The array must not be null.");
_array = array;
}
T[] result = (T[]) Array.newInstance(c, size);
int i = 0;
for (T element : list) { // Assumes iteration order is consistent
result[i++] = element; // Assign array element at index THEN increment counter
}
return result;
}
/**
* Determines if this object has a value equivalent to another object.
*
* @see Arrays#equals(Object[], Object[])
*/
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object other) {
if (!(other instanceof ArrayWrapper)) {
return false;
}
return Arrays.equals(_array, ((ArrayWrapper) other)._array);
}
/**
* Retrieves a reference to the wrapped array instance.
* @return The array wrapped by this instance.
*/
public E[] getArray() {
return this.array;
}
/**
* Gets the hash code represented by this objects value.
*
* @return This object's hash code.
* @see Arrays#hashCode(Object[])
*/
@Override
public int hashCode() {
return Arrays.hashCode(_array);
}
/**
* Set this wrapper to wrap a new array instance.
* @param array The new wrapped array.
*/
public void setArray(E[] array) {
Validate.notNull(array, "The array must not be null.");
this.array = array;
}
/**
* Converts an iterable element collection to an array of elements.
* The iteration order of the specified object will be used as the array element order.
*
* @param list The iterable of objects which will be converted to an array.
* @param c The type of the elements of the array.
* @return An array of elements in the specified iterable.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Iterable<? extends T> list, Class<T> c) {
int size = -1;
if (list instanceof Collection<?>) {
@SuppressWarnings("rawtypes")
Collection coll = (Collection) list;
size = coll.size();
}
/**
* Determines if this object has a value equivalent to another object.
* @see Arrays#equals(Object[], Object[])
*/
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object other) {
if (!(other instanceof ArrayWrapper)) {
return false;
}
return Arrays.equals(this.array, ((ArrayWrapper) other).array);
}
/**
* Gets the hash code represented by this objects value.
* @see Arrays#hashCode(Object[])
* @return This object's hash code.
*/
@Override
public int hashCode() {
return Arrays.hashCode(this.array);
}
if (size < 0) {
size = 0;
// Ugly hack: Count it ourselves
for (@SuppressWarnings("unused") T element : list) {
size++;
}
}
T[] result = (T[]) Array.newInstance(c, size);
int i = 0;
for (T element : list) { // Assumes iteration order is consistent
result[i++] = element; // Assign array element at index THEN increment counter
}
return result;
}
}

View File

@ -9,11 +9,11 @@ import java.io.IOException;
*/
interface JsonRepresentedObject {
/**
* Writes the JSON representation of this object to the specified writer.
* @param writer The JSON writer which will receive the object.
* @throws IOException If an error occurs writing to the stream.
*/
void writeJson(JsonWriter writer) throws IOException;
/**
* Writes the JSON representation of this object to the specified writer.
* @param writer The JSON writer which will receive the object.
* @throws IOException If an error occurs writing to the stream.
*/
public void writeJson(JsonWriter writer) throws IOException;
}

View File

@ -1,12 +1,12 @@
package com.plotsquared.bukkit.chat;
import com.google.gson.stream.JsonWriter;
import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.stream.JsonWriter;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
/**
* Represents a JSON string value.
* Writes by this object will not write name values nor begin/end objects in the JSON stream.
@ -14,34 +14,34 @@ import java.util.Map;
*/
final class JsonString implements JsonRepresentedObject, ConfigurationSerializable {
private final String value;
private String _value;
public JsonString(CharSequence value) {
this.value = value == null ? null : value.toString();
}
public JsonString(CharSequence value) {
_value = value == null ? null : value.toString();
}
public static JsonString deserialize(Map<String, Object> map) {
return new JsonString(map.get("stringValue").toString());
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.value(getValue());
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.value(getValue());
}
public String getValue() {
return _value;
}
public String getValue() {
return this.value;
}
public Map<String, Object> serialize() {
HashMap<String, Object> theSingleValue = new HashMap<String, Object>();
theSingleValue.put("stringValue", _value);
return theSingleValue;
}
@Override
public Map<String, Object> serialize() {
HashMap<String, Object> theSingleValue = new HashMap<>();
theSingleValue.put("stringValue", this.value);
return theSingleValue;
}
public static JsonString deserialize(Map<String, Object> map) {
return new JsonString(map.get("stringValue").toString());
}
@Override
public String toString() {
return _value;
}
@Override
public String toString() {
return this.value;
}
}

View File

@ -3,10 +3,10 @@ package com.plotsquared.bukkit.chat;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.gson.stream.JsonWriter;
import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable;
import com.intellectualcrafters.configuration.serialization.ConfigurationSerialization;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import java.io.IOException;
import java.util.ArrayList;
@ -19,133 +19,137 @@ import java.util.logging.Level;
*/
final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable {
static final BiMap<ChatColor, String> stylesToNames;
ChatColor color = ChatColor.WHITE;
ArrayList<ChatColor> styles = new ArrayList<ChatColor>();
String clickActionName = null, clickActionData = null, hoverActionName = null;
JsonRepresentedObject hoverActionData = null;
TextualComponent text = null;
String insertionData = null;
ArrayList<JsonRepresentedObject> translationReplacements = new ArrayList<JsonRepresentedObject>();
static {
ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
for (ChatColor style : ChatColor.values()) {
if (!style.isFormat()) {
continue;
}
MessagePart(final TextualComponent text) {
this.text = text;
}
String styleName;
switch (style) {
case MAGIC:
styleName = "obfuscated";
break;
case UNDERLINE:
styleName = "underlined";
break;
default:
styleName = style.name().toLowerCase();
break;
}
MessagePart() {
this.text = null;
}
builder.put(style, styleName);
}
stylesToNames = builder.build();
}
boolean hasText() {
return text != null;
}
static {
ConfigurationSerialization.registerClass(MessagePart.class);
}
@Override
@SuppressWarnings("unchecked")
public MessagePart clone() throws CloneNotSupportedException {
MessagePart obj = (MessagePart) super.clone();
obj.styles = (ArrayList<ChatColor>) styles.clone();
if (hoverActionData instanceof JsonString) {
obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue());
} else if (hoverActionData instanceof FancyMessage) {
obj.hoverActionData = ((FancyMessage) hoverActionData).clone();
}
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) translationReplacements.clone();
return obj;
ChatColor color = ChatColor.WHITE;
ArrayList<ChatColor> styles = new ArrayList<>();
String clickActionName = null, clickActionData = null, hoverActionName = null;
JsonRepresentedObject hoverActionData = null;
TextualComponent text = null;
String insertionData = null;
ArrayList<JsonRepresentedObject> translationReplacements = new ArrayList<>();
}
MessagePart(TextualComponent text) {
this.text = text;
}
static final BiMap<ChatColor, String> stylesToNames;
MessagePart() {
this.text = null;
}
static {
ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
for (final ChatColor style : ChatColor.values()) {
if (!style.isFormat()) {
continue;
}
@SuppressWarnings("unchecked")
public static MessagePart deserialize(Map<String, Object> serialized) {
MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
part.styles = (ArrayList<ChatColor>) serialized.get("styles");
part.color = ChatColor.getByChar(serialized.get("color").toString());
part.hoverActionName = (String) serialized.get("hoverActionName");
part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData");
part.clickActionName = (String) serialized.get("clickActionName");
part.clickActionData = (String) serialized.get("clickActionData");
part.insertionData = (String) serialized.get("insertion");
part.translationReplacements = (ArrayList<JsonRepresentedObject>) serialized.get("translationReplacements");
return part;
}
String styleName;
switch (style) {
case MAGIC:
styleName = "obfuscated";
break;
case UNDERLINE:
styleName = "underlined";
break;
default:
styleName = style.name().toLowerCase();
break;
}
boolean hasText() {
return this.text != null;
}
builder.put(style, styleName);
}
stylesToNames = builder.build();
}
@Override
@SuppressWarnings("unchecked")
public MessagePart clone() throws CloneNotSupportedException {
MessagePart obj = (MessagePart) super.clone();
obj.styles = (ArrayList<ChatColor>) this.styles.clone();
if (this.hoverActionData instanceof JsonString) {
obj.hoverActionData = new JsonString(((JsonString) this.hoverActionData).getValue());
} else if (this.hoverActionData instanceof FancyMessage) {
obj.hoverActionData = ((FancyMessage) this.hoverActionData).clone();
}
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) this.translationReplacements.clone();
return obj;
public void writeJson(JsonWriter json) {
try {
json.beginObject();
text.writeJson(json);
json.name("color").value(color.name().toLowerCase());
for (final ChatColor style : styles) {
json.name(stylesToNames.get(style)).value(true);
}
if (clickActionName != null && clickActionData != null) {
json.name("clickEvent")
.beginObject()
.name("action").value(clickActionName)
.name("value").value(clickActionData)
.endObject();
}
if (hoverActionName != null && hoverActionData != null) {
json.name("hoverEvent")
.beginObject()
.name("action").value(hoverActionName)
.name("value");
hoverActionData.writeJson(json);
json.endObject();
}
if (insertionData != null) {
json.name("insertion").value(insertionData);
}
if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) {
json.name("with").beginArray();
for (JsonRepresentedObject obj : translationReplacements) {
obj.writeJson(json);
}
json.endArray();
}
json.endObject();
} catch (IOException e) {
Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e);
}
}
}
public Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("text", text);
map.put("styles", styles);
map.put("color", color.getChar());
map.put("hoverActionName", hoverActionName);
map.put("hoverActionData", hoverActionData);
map.put("clickActionName", clickActionName);
map.put("clickActionData", clickActionData);
map.put("insertion", insertionData);
map.put("translationReplacements", translationReplacements);
return map;
}
@Override
public void writeJson(JsonWriter json) {
try {
json.beginObject();
this.text.writeJson(json);
json.name("color").value(this.color.name().toLowerCase());
for (ChatColor style : this.styles) {
json.name(stylesToNames.get(style)).value(true);
}
if ((this.clickActionName != null) && (this.clickActionData != null)) {
json.name("clickEvent").beginObject().name("action").value(this.clickActionName).name("value").value(this.clickActionData)
.endObject();
}
if ((this.hoverActionName != null) && (this.hoverActionData != null)) {
json.name("hoverEvent").beginObject().name("action").value(this.hoverActionName).name("value");
this.hoverActionData.writeJson(json);
json.endObject();
}
if (this.insertionData != null) {
json.name("insertion").value(this.insertionData);
}
if (!this.translationReplacements.isEmpty() && (this.text != null) && TextualComponent.isTranslatableText(this.text)) {
json.name("with").beginArray();
for (JsonRepresentedObject obj : this.translationReplacements) {
obj.writeJson(json);
}
json.endArray();
}
json.endObject();
} catch (IOException e) {
Bukkit.getLogger().log(Level.WARNING, "A problem occurred during writing of JSON string", e);
}
}
@SuppressWarnings("unchecked")
public static MessagePart deserialize(Map<String, Object> serialized) {
MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
part.styles = (ArrayList<ChatColor>) serialized.get("styles");
part.color = ChatColor.getByChar(serialized.get("color").toString());
part.hoverActionName = (String) serialized.get("hoverActionName");
part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData");
part.clickActionName = (String) serialized.get("clickActionName");
part.clickActionData = (String) serialized.get("clickActionData");
part.insertionData = (String) serialized.get("insertion");
part.translationReplacements = (ArrayList<JsonRepresentedObject>) serialized.get("translationReplacements");
return part;
}
@Override
public Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<>();
map.put("text", this.text);
map.put("styles", this.styles);
map.put("color", this.color.getChar());
map.put("hoverActionName", this.hoverActionName);
map.put("hoverActionData", this.hoverActionData);
map.put("clickActionName", this.clickActionName);
map.put("clickActionData", this.clickActionData);
map.put("insertion", this.insertionData);
map.put("translationReplacements", this.translationReplacements);
return map;
}
static {
ConfigurationSerialization.registerClass(MessagePart.class);
}
}

View File

@ -1,9 +1,8 @@
package com.plotsquared.bukkit.chat;
import com.intellectualcrafters.plot.PS;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
@ -15,191 +14,205 @@ import java.util.Map;
*/
public final class Reflection {
/**
* Stores loaded classes from the {@code net.minecraft.server} package.
*/
private static final Map<String, Class<?>> _loadedNMSClasses = new HashMap<>();
/**
* Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages).
*/
private static final Map<String, Class<?>> _loadedOBCClasses = new HashMap<>();
private static final Map<Class<?>, Map<String, Field>> _loadedFields = new HashMap<>();
/**
* Contains loaded methods in a cache.
* The map maps [types to maps of [method names to maps of [parameter types to method instances]]].
*/
private static final Map<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>> _loadedMethods = new HashMap<>();
private static String _versionString;
/**
* Gets the version string from the package name of the CraftBukkit server implementation.
* This is needed to bypass the JAR package name changing on each update.
* @return The version string of the OBC and NMS packages, <em>including the trailing dot</em>.
*/
public static synchronized String getVersion() {
return PS.get().IMP.getNMSPackage();
}
private Reflection() { }
/**
* Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this
* method simultaneously).
* @param className The name of the class, excluding the package, within NMS.
* @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
*/
public static synchronized Class<?> getNMSClass(String className) {
if (_loadedNMSClasses.containsKey(className)) {
return _loadedNMSClasses.get(className);
}
/**
* Gets the version string from the package name of the CraftBukkit server implementation.
* This is needed to bypass the JAR package name changing on each update.
*
* @return The version string of the OBC and NMS packages, <em>including the trailing dot</em>.
*/
public synchronized static String getVersion() {
if (_versionString == null) {
if (Bukkit.getServer() == null) {
// The server hasn't started, static initializer call?
return null;
}
String name = Bukkit.getServer().getClass().getPackage().getName();
_versionString = name.substring(name.lastIndexOf('.') + 1) + ".";
}
String fullName = "net.minecraft.server." + getVersion() + '.' + className;
Class<?> clazz;
try {
clazz = Class.forName(fullName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
_loadedNMSClasses.put(className, null);
return null;
}
_loadedNMSClasses.put(className, clazz);
return clazz;
}
return _versionString;
}
/**
* Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this
* method simultaneously).
* @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory
* .CraftItemStack}.
* @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
*/
public static synchronized Class<?> getOBCClass(String className) {
if (_loadedOBCClasses.containsKey(className)) {
return _loadedOBCClasses.get(className);
}
/**
* Stores loaded classes from the {@code net.minecraft.server} package.
*/
private static final Map<String, Class<?>> _loadedNMSClasses = new HashMap<String, Class<?>>();
String fullName = "org.bukkit.craftbukkit." + getVersion() + '.' + className;
Class<?> clazz;
try {
clazz = Class.forName(fullName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
_loadedOBCClasses.put(className, null);
return null;
}
_loadedOBCClasses.put(className, clazz);
return clazz;
}
/**
* Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages).
*/
private static final Map<String, Class<?>> _loadedOBCClasses = new HashMap<String, Class<?>>();
/**
* Attempts to get the NMS handle of a CraftBukkit object.
* <p>
* The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the
* runtime type of the specified object.
* </p>
* @param obj The object for which to retrieve an NMS handle.
* @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
*/
public static synchronized Object getHandle(Object obj) {
try {
return getMethod(obj.getClass(), "getHandle").invoke(obj);
} catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) {
e.printStackTrace();
return null;
}
}
/**
* Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
*
* @param className The name of the class, excluding the package, within NMS.
* @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getNMSClass(String className) {
if (_loadedNMSClasses.containsKey(className)) {
return _loadedNMSClasses.get(className);
}
/**
* Retrieves a {@link Field} instance declared by the specified class with the specified name.
* Java access modifiers are ignored during this retrieval.
* No guarantee is made as to whether the field returned will be an
* instance or static field.
* <p>
* A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that
* no field will be reflectively looked up twice.
* </p>
* <p>
* If a field is deemed suitable for return,
* {@link Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
* @param clazz The class which contains the field to retrieve.
* @param name The declared name of the field in the class.
* @return A field object with the specified name declared by the specified class.
* @see Class#getDeclaredField(String)
*/
public static synchronized Field getField(Class<?> clazz, String name) {
Map<String, Field> loaded;
if (!_loadedFields.containsKey(clazz)) {
loaded = new HashMap<>();
_loadedFields.put(clazz, loaded);
} else {
loaded = _loadedFields.get(clazz);
}
if (loaded.containsKey(name)) {
// If the field is loaded (or cached as not existing), return the relevant value, which might be null
return loaded.get(name);
}
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
loaded.put(name, field);
return field;
} catch (NoSuchFieldException | SecurityException e) {
// Error loading
e.printStackTrace();
// Cache field as not existing
loaded.put(name, null);
return null;
}
}
String fullName = "net.minecraft.server." + getVersion() + className;
Class<?> clazz = null;
try {
clazz = Class.forName(fullName);
} catch (Exception e) {
e.printStackTrace();
_loadedNMSClasses.put(className, null);
return null;
}
_loadedNMSClasses.put(className, clazz);
return clazz;
}
/**
* Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types.
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
* returned will be an instance or static field.
* <p>
* A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that
* no method will be reflectively looked up twice.
* </p>
* <p>
* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code
* true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
*
* <p>
* This method does <em>not</em> search superclasses of the specified type for methods with the specified signature.
* Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}.
* @param clazz The class which contains the method to retrieve.
* @param name The declared name of the method in the class.
* @param args The formal argument types of the method.
* @return A method object with the specified name declared by the specified class.
*/
public static synchronized Method getMethod(Class<?> clazz, String name, Class<?>... args) {
if (!_loadedMethods.containsKey(clazz)) {
_loadedMethods.put(clazz, new HashMap<String, Map<ArrayWrapper<Class<?>>, Method>>());
}
/**
* Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
*
* @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory.CraftItemStack}.
* @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getOBCClass(String className) {
if (_loadedOBCClasses.containsKey(className)) {
return _loadedOBCClasses.get(className);
}
Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
if (!loadedMethodNames.containsKey(name)) {
loadedMethodNames.put(name, new HashMap<ArrayWrapper<Class<?>>, Method>());
}
String fullName = "org.bukkit.craftbukkit." + getVersion() + className;
Class<?> clazz = null;
try {
clazz = Class.forName(fullName);
} catch (Exception e) {
e.printStackTrace();
_loadedOBCClasses.put(className, null);
return null;
}
_loadedOBCClasses.put(className, clazz);
return clazz;
}
Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
ArrayWrapper<Class<?>> wrappedArg = new ArrayWrapper<>(args);
if (loadedSignatures.containsKey(wrappedArg)) {
return loadedSignatures.get(wrappedArg);
}
/**
* Attempts to get the NMS handle of a CraftBukkit object.
* <p>
* The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the runtime type of the specified object.
* </p>
*
* @param obj The object for which to retrieve an NMS handle.
* @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
*/
public synchronized static Object getHandle(Object obj) {
try {
return getMethod(obj.getClass(), "getHandle").invoke(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
for (Method m : clazz.getMethods()) {
if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
m.setAccessible(true);
loadedSignatures.put(wrappedArg, m);
return m;
}
}
loadedSignatures.put(wrappedArg, null);
return null;
}
private static final Map<Class<?>, Map<String, Field>> _loadedFields = new HashMap<Class<?>, Map<String, Field>>();
/**
* Retrieves a {@link Field} instance declared by the specified class with the specified name.
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
* returned will be an instance or static field.
* <p>
* A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that
* no field will be reflectively looked up twice.
* </p>
* <p>
* If a field is deemed suitable for return, {@link Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
*
* @param clazz The class which contains the field to retrieve.
* @param name The declared name of the field in the class.
* @return A field object with the specified name declared by the specified class.
* @see Class#getDeclaredField(String)
*/
public synchronized static Field getField(Class<?> clazz, String name) {
Map<String, Field> loaded;
if (!_loadedFields.containsKey(clazz)) {
loaded = new HashMap<String, Field>();
_loadedFields.put(clazz, loaded);
} else {
loaded = _loadedFields.get(clazz);
}
if (loaded.containsKey(name)) {
// If the field is loaded (or cached as not existing), return the relevant value, which might be null
return loaded.get(name);
}
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
loaded.put(name, field);
return field;
} catch (Exception e) {
// Error loading
e.printStackTrace();
// Cache field as not existing
loaded.put(name, null);
return null;
}
}
/**
* Contains loaded methods in a cache.
* The map maps [types to maps of [method names to maps of [parameter types to method instances]]].
*/
private static final Map<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>> _loadedMethods = new HashMap<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>>();
/**
* Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types.
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
* returned will be an instance or static field.
* <p>
* A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that
* no method will be reflectively looked up twice.
* </p>
* <p>
* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
* <p>
* This method does <em>not</em> search superclasses of the specified type for methods with the specified signature.
* Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}.
*
* @param clazz The class which contains the method to retrieve.
* @param name The declared name of the method in the class.
* @param args The formal argument types of the method.
* @return A method object with the specified name declared by the specified class.
*/
public synchronized static Method getMethod(Class<?> clazz, String name, Class<?>... args) {
if (!_loadedMethods.containsKey(clazz)) {
_loadedMethods.put(clazz, new HashMap<String, Map<ArrayWrapper<Class<?>>, Method>>());
}
Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
if (!loadedMethodNames.containsKey(name)) {
loadedMethodNames.put(name, new HashMap<ArrayWrapper<Class<?>>, Method>());
}
Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
ArrayWrapper<Class<?>> wrappedArg = new ArrayWrapper<Class<?>>(args);
if (loadedSignatures.containsKey(wrappedArg)) {
return loadedSignatures.get(wrappedArg);
}
for (Method m : clazz.getMethods()) {
if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
m.setAccessible(true);
loadedSignatures.put(wrappedArg, m);
return m;
}
}
loadedSignatures.put(wrappedArg, null);
return null;
}
}

View File

@ -3,8 +3,8 @@ package com.plotsquared.bukkit.chat;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.gson.stream.JsonWriter;
import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable;
import com.intellectualcrafters.configuration.serialization.ConfigurationSerialization;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import java.io.IOException;
import java.util.HashMap;
@ -18,285 +18,280 @@ import java.util.Map;
*/
public abstract class TextualComponent implements Cloneable {
static {
ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class);
ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
}
static {
ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class);
ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
}
static TextualComponent deserialize(Map<String, Object> map) {
if (map.containsKey("key") && (map.size() == 2) && map.containsKey("value")) {
// Arbitrary text component
return ArbitraryTextTypeComponent.deserialize(map);
} else if ((map.size() >= 2) && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) {
// Complex JSON object
return ComplexTextTypeComponent.deserialize(map);
}
@Override
public String toString() {
return getReadableString();
}
return null;
}
/**
* @return The JSON key used to represent text components of this type.
*/
public abstract String getKey();
static boolean isTextKey(String key) {
return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
}
/**
* @return A readable String
*/
public abstract String getReadableString();
static boolean isTranslatableText(TextualComponent component) {
return (component instanceof ComplexTextTypeComponent) && component.getKey().equals("translate");
}
/**
* Clones a textual component instance.
* The returned object should not reference this textual component instance, but should maintain the same key and value.
*/
@Override
public abstract TextualComponent clone() throws CloneNotSupportedException;
/**
* Create a textual component representing a string literal.
* This is the default type of textual component when a single string literal is given to a method.
* @param textValue The text which will be represented.
* @return The text component representing the specified literal text.
*/
public static TextualComponent rawText(String textValue) {
return new ArbitraryTextTypeComponent("text", textValue);
}
/**
* Writes the text data represented by this textual component to the specified JSON writer object.
* A new object within the writer is not started.
*
* @param writer The object to which to write the JSON data.
* @throws IOException If an error occurs while writing to the stream.
*/
public abstract void writeJson(JsonWriter writer) throws IOException;
/**
* Create a textual component representing a localized string.
* The client will see this text component as their localized version of the specified string <em>key</em>, which can be overridden by a
* resource pack.
* <p>
* If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to
* the client.
* </p>
* @param translateKey The string key which maps to localized text.
* @return The text component representing the specified localized text.
*/
public static TextualComponent localizedText(String translateKey) {
return new ArbitraryTextTypeComponent("translate", translateKey);
}
static TextualComponent deserialize(Map<String, Object> map) {
if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) {
// Arbitrary text component
return ArbitraryTextTypeComponent.deserialize(map);
} else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) {
// Complex JSON object
return ComplexTextTypeComponent.deserialize(map);
}
private static void throwUnsupportedSnapshot() {
throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
}
return null;
}
/**
* Create a textual component representing a scoreboard value.
* The client will see their own score for the specified objective as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
* @param scoreboardObjective The name of the objective for which to display the score.
* @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during
* JSON serialization.
*/
public static TextualComponent objectiveScore(String scoreboardObjective) {
return objectiveScore("*", scoreboardObjective);
}
static boolean isTextKey(String key) {
return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
}
/**
* Create a textual component representing a scoreboard value.
* The client will see the score of the specified player for the specified objective as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
* @param playerName The name of the player whos score will be shown. If this string represents the single-character sequence "*", the viewing
* player's score will be displayed.
* Standard minecraft selectors (@a, @p, etc) are <em>not</em> supported.
* @param scoreboardObjective The name of the objective for which to display the score.
* @return The text component representing the specified scoreboard score for the specified player, or {@code null} if an error occurs during
* JSON serialization.
*/
public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) {
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
// OVERLOADS documentation accordingly
static boolean isTranslatableText(TextualComponent component) {
return component instanceof ComplexTextTypeComponent && ((ComplexTextTypeComponent) component).getKey().equals("translate");
}
return new ComplexTextTypeComponent("score",
ImmutableMap.<String, String>builder().put("name", playerName).put("objective", scoreboardObjective).build());
}
/**
* Internal class used to represent all types of text components.
* Exception validating done is on keys and values.
*/
private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
/**
* Create a textual component representing a player name, retrievable by using a standard minecraft selector.
* The client will see the players or entities captured by the specified selector as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
* @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in
* the place of this text component.
* @return The text component representing the name of the entities captured by the selector.
*/
public static TextualComponent selector(String selector) {
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
// OVERLOADS documentation accordingly
public ArbitraryTextTypeComponent(String key, String value) {
setKey(key);
setValue(value);
}
return new ArbitraryTextTypeComponent("selector", selector);
}
@Override
public String getKey() {
return _key;
}
@Override
public String toString() {
return getReadableString();
}
public void setKey(String key) {
Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
_key = key;
}
/**
* @return The JSON key used to represent text components of this type.
*/
public abstract String getKey();
public String getValue() {
return _value;
}
/**
* @return A readable String
*/
public abstract String getReadableString();
public void setValue(String value) {
Preconditions.checkArgument(value != null, "The value must be specified.");
_value = value;
}
/**
* Clones a textual component instance.
* The returned object should not reference this textual component instance, but should maintain the same key and value.
*/
@Override
public abstract TextualComponent clone() throws CloneNotSupportedException;
private String _key;
private String _value;
/**
* Writes the text data represented by this textual component to the specified JSON writer object.
* A new object within the writer is not started.
* @param writer The object to which to write the JSON data.
* @throws IOException If an error occurs while writing to the stream.
*/
public abstract void writeJson(JsonWriter writer) throws IOException;
@Override
public TextualComponent clone() throws CloneNotSupportedException {
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
return new ArbitraryTextTypeComponent(getKey(), getValue());
}
/**
* Internal class used to represent all types of text components.
* Exception validating done is on keys and values.
*/
private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.name(getKey()).value(getValue());
}
private String _key;
private String _value;
@SuppressWarnings("serial")
public Map<String, Object> serialize() {
return new HashMap<String, Object>() {{
put("key", getKey());
put("value", getValue());
}};
}
public ArbitraryTextTypeComponent(String key, String value) {
setKey(key);
setValue(value);
}
public static ArbitraryTextTypeComponent deserialize(Map<String, Object> map) {
return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
}
public static ArbitraryTextTypeComponent deserialize(Map<String, Object> map) {
return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
}
@Override
public String getReadableString() {
return getValue();
}
}
@Override
public String getKey() {
return this._key;
}
/**
* Internal class used to represent a text component with a nested JSON value.
* Exception validating done is on keys and values.
*/
private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
public void setKey(String key) {
Preconditions.checkArgument((key != null) && !key.isEmpty(), "The key must be specified.");
this._key = key;
}
public ComplexTextTypeComponent(String key, Map<String, String> values) {
setKey(key);
setValue(values);
}
public String getValue() {
return this._value;
}
@Override
public String getKey() {
return _key;
}
public void setValue(String value) {
Preconditions.checkArgument(value != null, "The value must be specified.");
this._value = value;
}
public void setKey(String key) {
Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
_key = key;
}
@Override
public TextualComponent clone() {
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
return new ArbitraryTextTypeComponent(getKey(), getValue());
}
public Map<String, String> getValue() {
return _value;
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.name(getKey()).value(getValue());
}
public void setValue(Map<String, String> value) {
Preconditions.checkArgument(value != null, "The value must be specified.");
_value = value;
}
@Override
@SuppressWarnings("serial")
public Map<String, Object> serialize() {
return new HashMap<String, Object>() {
{
put("key", getKey());
put("value", getValue());
}
};
}
private String _key;
private Map<String, String> _value;
@Override
public String getReadableString() {
return getValue();
}
}
@Override
public TextualComponent clone() throws CloneNotSupportedException {
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
return new ComplexTextTypeComponent(getKey(), getValue());
}
/**
* Internal class used to represent a text component with a nested JSON value.
* Exception validating done is on keys and values.
*/
private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.name(getKey());
writer.beginObject();
for (Map.Entry<String, String> jsonPair : _value.entrySet()) {
writer.name(jsonPair.getKey()).value(jsonPair.getValue());
}
writer.endObject();
}
private String _key;
private Map<String, String> _value;
@SuppressWarnings("serial")
public Map<String, Object> serialize() {
return new java.util.HashMap<String, Object>() {{
put("key", getKey());
for (Map.Entry<String, String> valEntry : getValue().entrySet()) {
put("value." + valEntry.getKey(), valEntry.getValue());
}
}};
}
public ComplexTextTypeComponent(String key, Map<String, String> values) {
setKey(key);
setValue(values);
}
public static ComplexTextTypeComponent deserialize(Map<String, Object> map) {
String key = null;
Map<String, String> value = new HashMap<String, String>();
for (Map.Entry<String, Object> valEntry : map.entrySet()) {
if (valEntry.getKey().equals("key")) {
key = (String) valEntry.getValue();
} else if (valEntry.getKey().startsWith("value.")) {
value.put(((String) valEntry.getKey()).substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
}
}
return new ComplexTextTypeComponent(key, value);
}
public static ComplexTextTypeComponent deserialize(Map<String, Object> map) {
String key = null;
Map<String, String> value = new HashMap<>();
for (Map.Entry<String, Object> valEntry : map.entrySet()) {
if (valEntry.getKey().equals("key")) {
key = (String) valEntry.getValue();
} else if (valEntry.getKey().startsWith("value.")) {
value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
}
}
return new ComplexTextTypeComponent(key, value);
}
@Override
public String getReadableString() {
return getKey();
}
}
@Override
public String getKey() {
return this._key;
}
/**
* Create a textual component representing a string literal.
* This is the default type of textual component when a single string literal is given to a method.
*
* @param textValue The text which will be represented.
* @return The text component representing the specified literal text.
*/
public static TextualComponent rawText(String textValue) {
return new ArbitraryTextTypeComponent("text", textValue);
}
public void setKey(String key) {
Preconditions.checkArgument((key != null) && !key.isEmpty(), "The key must be specified.");
this._key = key;
}
public Map<String, String> getValue() {
return this._value;
}
/**
* Create a textual component representing a localized string.
* The client will see this text component as their localized version of the specified string <em>key</em>, which can be overridden by a resource pack.
* <p>
* If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to the client.
* </p>
*
* @param translateKey The string key which maps to localized text.
* @return The text component representing the specified localized text.
*/
public static TextualComponent localizedText(String translateKey) {
return new ArbitraryTextTypeComponent("translate", translateKey);
}
public void setValue(Map<String, String> value) {
Preconditions.checkArgument(value != null, "The value must be specified.");
this._value = value;
}
private static void throwUnsupportedSnapshot() {
throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
}
@Override
public TextualComponent clone() {
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
return new ComplexTextTypeComponent(getKey(), getValue());
}
/**
* Create a textual component representing a scoreboard value.
* The client will see their own score for the specified objective as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
*
* @param scoreboardObjective The name of the objective for which to display the score.
* @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during JSON serialization.
*/
public static TextualComponent objectiveScore(String scoreboardObjective) {
return objectiveScore("*", scoreboardObjective);
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.name(getKey());
writer.beginObject();
for (Map.Entry<String, String> jsonPair : this._value.entrySet()) {
writer.name(jsonPair.getKey()).value(jsonPair.getValue());
}
writer.endObject();
}
/**
* Create a textual component representing a scoreboard value.
* The client will see the score of the specified player for the specified objective as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
*
* @param playerName The name of the player whos score will be shown. If this string represents the single-character sequence "*", the viewing player's score will be displayed.
* Standard minecraft selectors (@a, @p, etc) are <em>not</em> supported.
* @param scoreboardObjective The name of the objective for which to display the score.
* @return The text component representing the specified scoreboard score for the specified player, or {@code null} if an error occurs during JSON serialization.
*/
public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) {
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly
@Override
@SuppressWarnings("serial")
public Map<String, Object> serialize() {
return new HashMap<String, Object>() {
{
put("key", getKey());
for (Map.Entry<String, String> valEntry : getValue().entrySet()) {
put("value." + valEntry.getKey(), valEntry.getValue());
}
}
};
}
return new ComplexTextTypeComponent("score", ImmutableMap.<String, String>builder()
.put("name", playerName)
.put("objective", scoreboardObjective)
.build());
}
@Override
public String getReadableString() {
return getKey();
}
}
/**
* Create a textual component representing a player name, retrievable by using a standard minecraft selector.
* The client will see the players or entities captured by the specified selector as the text represented by this component.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
*
* @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in the place of this text component.
* @return The text component representing the name of the entities captured by the selector.
*/
public static TextualComponent selector(String selector) {
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly
return new ArbitraryTextTypeComponent("selector", selector);
}
}