Allow for plugins/mods to register a clipboard format
*Use reflection to add new enum instances
This commit is contained in:
parent
b32b633248
commit
91b2347b96
@ -0,0 +1,24 @@
|
||||
package com.boydti.fawe.object.clipboard;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
public abstract class AbstractClipboardFormat implements IClipboardFormat {
|
||||
private final String name;
|
||||
private final HashSet<String> aliases;
|
||||
|
||||
public AbstractClipboardFormat(String name, String... aliases) {
|
||||
this.name = name;
|
||||
this.aliases = new HashSet<>(Arrays.asList(aliases));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashSet<String> getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.boydti.fawe.object.clipboard;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Set;
|
||||
|
||||
public interface IClipboardFormat {
|
||||
/**
|
||||
* Returns the name of this format.
|
||||
* @return The name of the format
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Create a reader.
|
||||
*
|
||||
* @param inputStream the input stream
|
||||
* @return a reader
|
||||
* @throws java.io.IOException thrown on I/O error
|
||||
*/
|
||||
ClipboardReader getReader(InputStream inputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a writer.
|
||||
*
|
||||
* @param outputStream the output stream
|
||||
* @return a writer
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
ClipboardWriter getWriter(OutputStream outputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Return whether the given file is of this format.
|
||||
*
|
||||
* @param file the file
|
||||
* @return true if the given file is of this format
|
||||
*/
|
||||
boolean isFormat(File file);
|
||||
|
||||
/**
|
||||
* Get the default extension
|
||||
* @return
|
||||
*/
|
||||
String getExtension();
|
||||
|
||||
/**
|
||||
* Get a set of aliases.
|
||||
*
|
||||
* @return a set of aliases
|
||||
*/
|
||||
Set<String> getAliases();
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -11,6 +14,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import sun.reflect.ConstructorAccessor;
|
||||
import sun.reflect.FieldAccessor;
|
||||
import sun.reflect.ReflectionFactory;
|
||||
|
||||
/**
|
||||
* @author DPOH-VAR
|
||||
@ -58,29 +64,133 @@ public class ReflectionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, V> Map<T, V> getMap(Map<T, V> map) {
|
||||
try {
|
||||
Class<? extends Map> clazz = map.getClass();
|
||||
Field m = clazz.getDeclaredField("m");
|
||||
m.setAccessible(true);
|
||||
return (Map<T, V>) m.get(map);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName) {
|
||||
|
||||
public static <T> List<T> getList(List<T> list) {
|
||||
try {
|
||||
Class<? extends List> clazz = (Class<? extends List>) Class.forName("java.util.Collections$UnmodifiableList");
|
||||
Field m = clazz.getDeclaredField("list");
|
||||
m.setAccessible(true);
|
||||
return (List<T>) m.get(list);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return list;
|
||||
// 0. Sanity checks
|
||||
if (!Enum.class.isAssignableFrom(enumType)) {
|
||||
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
|
||||
}
|
||||
// 1. Lookup "$VALUES" holder in enum class and get previous enum instances
|
||||
Field valuesField = null;
|
||||
Field[] fields = enumType.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getName().contains("$VALUES")) {
|
||||
valuesField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AccessibleObject.setAccessible(new Field[] { valuesField }, true);
|
||||
|
||||
try {
|
||||
|
||||
// 2. Copy it
|
||||
T[] previousValues = (T[]) valuesField.get(enumType);
|
||||
List values = new ArrayList(Arrays.asList(previousValues));
|
||||
|
||||
// 3. build new enum
|
||||
T newValue = (T) makeEnum(enumType, // The target enum class
|
||||
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
|
||||
values.size(),
|
||||
new Class<?>[] {}, // can be used to pass values to the enum constuctor
|
||||
new Object[] {}); // can be used to pass values to the enum constuctor
|
||||
|
||||
// 4. add new value
|
||||
values.add(newValue);
|
||||
|
||||
// 5. Set new values field
|
||||
setFailsafeFieldValue(valuesField, null,
|
||||
values.toArray((T[]) Array.newInstance(enumType, 0)));
|
||||
|
||||
// 6. Clean enum cache
|
||||
cleanEnumCache(enumType);
|
||||
return newValue;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object makeEnum(Class<?> enumClass, String value, int ordinal,
|
||||
Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
|
||||
Object[] parms = new Object[additionalValues.length + 2];
|
||||
parms[0] = value;
|
||||
parms[1] = Integer.valueOf(ordinal);
|
||||
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
|
||||
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
|
||||
}
|
||||
|
||||
private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass,
|
||||
Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
|
||||
parameterTypes[0] = String.class;
|
||||
parameterTypes[1] = int.class;
|
||||
System.arraycopy(additionalParameterTypes, 0,
|
||||
parameterTypes, 2, additionalParameterTypes.length);
|
||||
return ReflectionFactory.getReflectionFactory().newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
|
||||
}
|
||||
|
||||
private static void setFailsafeFieldValue(Field field, Object target, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
|
||||
// let's make the field accessible
|
||||
field.setAccessible(true);
|
||||
|
||||
// next we change the modifier in the Field instance to
|
||||
// not be final anymore, thus tricking reflection into
|
||||
// letting us modify the static final field
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
int modifiers = modifiersField.getInt(field);
|
||||
|
||||
// blank out the final bit in the modifiers int
|
||||
modifiers &= ~Modifier.FINAL;
|
||||
modifiersField.setInt(field, modifiers);
|
||||
|
||||
FieldAccessor fa = ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
|
||||
fa.set(target, value);
|
||||
}
|
||||
|
||||
private static void blankField(Class<?> enumClass, String fieldName)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Field field : Class.class.getDeclaredFields()) {
|
||||
if (field.getName().contains(fieldName)) {
|
||||
AccessibleObject.setAccessible(new Field[] { field }, true);
|
||||
setFailsafeFieldValue(field, enumClass, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void cleanEnumCache(Class<?> enumClass)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
|
||||
blankField(enumClass, "enumConstants"); // IBM JDK
|
||||
}
|
||||
|
||||
public static <T, V> Map<T, V> getMap(Map<T, V> map) {
|
||||
try {
|
||||
Class<? extends Map> clazz = map.getClass();
|
||||
Field m = clazz.getDeclaredField("m");
|
||||
m.setAccessible(true);
|
||||
return (Map<T, V>) m.get(map);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> getList(List<T> list) {
|
||||
try {
|
||||
Class<? extends List> clazz = (Class<? extends List>) Class.forName("java.util.Collections$UnmodifiableList");
|
||||
Field m = clazz.getDeclaredField("list");
|
||||
m.setAccessible(true);
|
||||
return (List<T>) m.get(list);
|
||||
} catch (Throwable e) {
|
||||
MainUtil.handleError(e);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getNmsClass(final String name) {
|
||||
final String className = "net.minecraft.server." + getVersion() + "." + name;
|
||||
|
@ -20,12 +20,15 @@
|
||||
package com.sk89q.worldedit.extent.clipboard.io;
|
||||
|
||||
import com.boydti.fawe.object.FaweOutputStream;
|
||||
import com.boydti.fawe.object.clipboard.AbstractClipboardFormat;
|
||||
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
|
||||
import com.boydti.fawe.object.clipboard.IClipboardFormat;
|
||||
import com.boydti.fawe.object.schematic.FaweFormat;
|
||||
import com.boydti.fawe.object.schematic.PNGWriter;
|
||||
import com.boydti.fawe.object.schematic.Schematic;
|
||||
import com.boydti.fawe.object.schematic.StructureFormat;
|
||||
import com.boydti.fawe.util.MainUtil;
|
||||
import com.boydti.fawe.util.ReflectionUtils;
|
||||
import com.sk89q.jnbt.NBTConstants;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
@ -37,13 +40,10 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import javax.annotation.Nullable;
|
||||
@ -59,7 +59,7 @@ public enum ClipboardFormat {
|
||||
/**
|
||||
* The Schematic format used by many software.
|
||||
*/
|
||||
SCHEMATIC("mcedit", "mce", "schematic") {
|
||||
SCHEMATIC(new AbstractClipboardFormat("SCHEMATIC", "mcedit", "mce", "schematic") {
|
||||
@Override
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
inputStream = new BufferedInputStream(inputStream);
|
||||
@ -108,12 +108,13 @@ public enum ClipboardFormat {
|
||||
public String getExtension() {
|
||||
return "schematic";
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* The structure block format:
|
||||
* http://minecraft.gamepedia.com/Structure_block_file_format
|
||||
*/
|
||||
STRUCTURE("structure", "nbt") {
|
||||
STRUCTURE(new AbstractClipboardFormat("STRUCTURE", "structure", "nbt") {
|
||||
@Override
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
inputStream = new BufferedInputStream(inputStream);
|
||||
@ -143,12 +144,13 @@ public enum ClipboardFormat {
|
||||
public String getExtension() {
|
||||
return "nbt";
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Isometric PNG writer
|
||||
*/
|
||||
PNG("png", "image") {
|
||||
PNG(new AbstractClipboardFormat("PNG", "png", "image") {
|
||||
|
||||
@Override
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
return null;
|
||||
@ -159,16 +161,16 @@ public enum ClipboardFormat {
|
||||
return new PNGWriter(new BufferedOutputStream(outputStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return "png";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFormat(File file) {
|
||||
return file.getName().endsWith(".png");
|
||||
}
|
||||
},
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return "png";
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* The FAWE file format:
|
||||
@ -185,64 +187,17 @@ public enum ClipboardFormat {
|
||||
* FaweFormat: compression/mode -> Any/Any (slower)
|
||||
*
|
||||
*/
|
||||
FAWE("fawe") {
|
||||
/**
|
||||
* Read a clipboard from a compressed stream (the first byte indicates the compression level)
|
||||
* @param inputStream the input stream
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
FAWE(new AbstractClipboardFormat("FAWE", "fawe") {
|
||||
@Override
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
return new FaweFormat(MainUtil.getCompressedIS(inputStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a clipboard to a stream with compression level 8
|
||||
* @param outputStream the output stream
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public ClipboardWriter getWriter(OutputStream outputStream) throws IOException {
|
||||
return getWriter(outputStream, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a clipboard to a stream
|
||||
* @param os
|
||||
* @param compression
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public ClipboardWriter getWriter(OutputStream os, int compression) throws IOException {
|
||||
FaweFormat writer = new FaweFormat(new FaweOutputStream(os));
|
||||
writer.compress(compression);
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read or write blocks ids to a file
|
||||
* @param file
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public DiskOptimizedClipboard getUncompressedReadWrite(File file) throws IOException {
|
||||
return new DiskOptimizedClipboard(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read or write block ids to a new file
|
||||
* @param width
|
||||
* @param height
|
||||
* @param length
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public DiskOptimizedClipboard createUncompressedReadWrite(int width, int height, int length, File file) {
|
||||
return new DiskOptimizedClipboard(width, height, length, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFormat(File file) {
|
||||
return file.getName().endsWith(".fawe") || file.getName().endsWith(".bd");
|
||||
@ -252,21 +207,45 @@ public enum ClipboardFormat {
|
||||
public String getExtension() {
|
||||
return "fawe";
|
||||
}
|
||||
},
|
||||
|
||||
public ClipboardWriter getWriter(OutputStream os, int compression) throws IOException {
|
||||
FaweFormat writer = new FaweFormat(new FaweOutputStream(os));
|
||||
writer.compress(compression);
|
||||
return writer;
|
||||
}
|
||||
|
||||
public DiskOptimizedClipboard getUncompressedReadWrite(File file) throws IOException {
|
||||
return new DiskOptimizedClipboard(file);
|
||||
}
|
||||
|
||||
public DiskOptimizedClipboard createUncompressedReadWrite(int width, int height, int length, File file) {
|
||||
return new DiskOptimizedClipboard(width, height, length, file);
|
||||
}
|
||||
}),
|
||||
|
||||
;
|
||||
|
||||
private static final Map<String, ClipboardFormat> aliasMap = new HashMap<String, ClipboardFormat>();
|
||||
private static final Map<String, ClipboardFormat> aliasMap;
|
||||
static {
|
||||
aliasMap = new ConcurrentHashMap<>();
|
||||
for (ClipboardFormat emum : ClipboardFormat.values()) {
|
||||
for (String alias : emum.getAliases()) {
|
||||
aliasMap.put(alias, emum);
|
||||
}
|
||||
}
|
||||
}
|
||||
private IClipboardFormat format;
|
||||
|
||||
private final String[] aliases;
|
||||
ClipboardFormat() {
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param aliases an array of aliases by which this format may be referred to
|
||||
*/
|
||||
ClipboardFormat(String ... aliases) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
ClipboardFormat(IClipboardFormat format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public IClipboardFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,7 +254,7 @@ public enum ClipboardFormat {
|
||||
* @return a set of aliases
|
||||
*/
|
||||
public Set<String> getAliases() {
|
||||
return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(aliases)));
|
||||
return format.getAliases();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -285,7 +264,9 @@ public enum ClipboardFormat {
|
||||
* @return a reader
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
public abstract ClipboardReader getReader(InputStream inputStream) throws IOException;
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
return format.getReader(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a writer.
|
||||
@ -294,7 +275,9 @@ public enum ClipboardFormat {
|
||||
* @return a writer
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
public abstract ClipboardWriter getWriter(OutputStream outputStream) throws IOException;
|
||||
public ClipboardWriter getWriter(OutputStream outputStream) throws IOException {
|
||||
return format.getWriter(outputStream);
|
||||
}
|
||||
|
||||
public Schematic load(File file) throws IOException {
|
||||
return load(new FileInputStream(file));
|
||||
@ -308,7 +291,9 @@ public enum ClipboardFormat {
|
||||
* Get the file extension used
|
||||
* @return file extension string
|
||||
*/
|
||||
public abstract String getExtension();
|
||||
public String getExtension() {
|
||||
return format.getExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given file is of this format.
|
||||
@ -316,14 +301,8 @@ public enum ClipboardFormat {
|
||||
* @param file the file
|
||||
* @return true if the given file is of this format
|
||||
*/
|
||||
public abstract boolean isFormat(File file);
|
||||
|
||||
static {
|
||||
for (ClipboardFormat format : EnumSet.allOf(ClipboardFormat.class)) {
|
||||
for (String key : format.aliases) {
|
||||
aliasMap.put(key, format);
|
||||
}
|
||||
}
|
||||
public boolean isFormat(File file) {
|
||||
return format.isFormat(file);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -347,7 +326,6 @@ public enum ClipboardFormat {
|
||||
@Nullable
|
||||
public static ClipboardFormat findByFile(File file) {
|
||||
checkNotNull(file);
|
||||
|
||||
for (ClipboardFormat format : EnumSet.allOf(ClipboardFormat.class)) {
|
||||
if (format.isFormat(file)) {
|
||||
return format;
|
||||
@ -357,6 +335,15 @@ public enum ClipboardFormat {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ClipboardFormat addFormat(IClipboardFormat instance) {
|
||||
ClipboardFormat newEnum = ReflectionUtils.addEnum(ClipboardFormat.class, instance.getName());
|
||||
newEnum.format = instance;
|
||||
for (String alias : newEnum.getAliases()) {
|
||||
aliasMap.put(alias, newEnum);
|
||||
}
|
||||
return newEnum;
|
||||
}
|
||||
|
||||
public static Class<?> inject() {
|
||||
return ClipboardFormat.class;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user