diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractClipboardFormat.java b/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractClipboardFormat.java new file mode 100644 index 00000000..7ebf9bcb --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractClipboardFormat.java @@ -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 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 getAliases() { + return aliases; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/IClipboardFormat.java b/core/src/main/java/com/boydti/fawe/object/clipboard/IClipboardFormat.java new file mode 100644 index 00000000..4195b68b --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/IClipboardFormat.java @@ -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 getAliases(); +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java b/core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java index 51ced849..de25643f 100644 --- a/core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java +++ b/core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java @@ -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 Map getMap(Map map) { - try { - Class clazz = map.getClass(); - Field m = clazz.getDeclaredField("m"); - m.setAccessible(true); - return (Map) m.get(map); - } catch (Throwable e) { - MainUtil.handleError(e); - return map; - } -} + @SuppressWarnings("unchecked") + public static > T addEnum(Class enumType, String enumName) { -public static List getList(List list) { - try { - Class clazz = (Class) Class.forName("java.util.Collections$UnmodifiableList"); - Field m = clazz.getDeclaredField("list"); - m.setAccessible(true); - return (List) 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 Map getMap(Map map) { + try { + Class clazz = map.getClass(); + Field m = clazz.getDeclaredField("m"); + m.setAccessible(true); + return (Map) m.get(map); + } catch (Throwable e) { + MainUtil.handleError(e); + return map; + } + } + + public static List getList(List list) { + try { + Class clazz = (Class) Class.forName("java.util.Collections$UnmodifiableList"); + Field m = clazz.getDeclaredField("list"); + m.setAccessible(true); + return (List) 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; diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index 39f90a7f..1f28bbdb 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -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 aliasMap = new HashMap(); + private static final Map 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 getAliases() { - return Collections.unmodifiableSet(new HashSet(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; }