From 0b292af3b12393b27145a4c1fb5c1f0553d4a802 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 02:09:45 +0100 Subject: [PATCH] Add the ability to write to final fields, even if it is compiled. --- .../com/comphenix/protocol/CommandPacket.java | 1 - .../protocol/reflect/ObjectCloner.java | 6 +- .../protocol/reflect/StructureModifier.java | 88 +++++++++++++++++-- .../compiler/CompiledStructureModifier.java | 24 +++++ 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index d09189e6..dbe85114 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -30,7 +30,6 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; import com.google.common.collect.DiscreteDomains; -import com.google.common.collect.Maps; import com.google.common.collect.Range; import com.google.common.collect.Ranges; import com.google.common.collect.Sets; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java index a9807c32..5475e60d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java @@ -66,8 +66,10 @@ public class ObjectCloner { // Copy every field try { for (int i = 0; i < modifierSource.size(); i++) { - Object value = modifierSource.read(i); - modifierDest.write(i, value); + if (!modifierDest.isReadOnly(i)) { + Object value = modifierSource.read(i); + modifierDest.write(i, value); + } // System.out.println(String.format("Writing value %s to %s", // value, modifier.getFields().get(i).getName())); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index cd232710..31715877 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -61,6 +61,17 @@ public class StructureModifier { // Whether or subclasses should handle conversion protected boolean customConvertHandling; + // Whether or not to automatically compile the structure modifier + protected boolean useStructureCompiler; + + /** + * Creates a structure modifier. + * @param targetType - the structure to modify. + */ + public StructureModifier(Class targetType) { + this(targetType, null, true); + } + /** * Creates a structure modifier. * @param targetType - the structure to modify. @@ -68,10 +79,22 @@ public class StructureModifier { * @param requireDefault - whether or not we will be using writeDefaults(). */ public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) { + this(targetType, superclassExclude, requireDefault, true); + } + + /** + * Creates a structure modifier. + * @param targetType - the structure to modify. + * @param superclassExclude - a superclass to exclude. + * @param requireDefault - whether or not we will be using writeDefaults(). + * @param useStructureModifier - whether or not to automatically compile this structure modifier. + */ + public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault, boolean useStructureCompiler) { List fields = getFields(targetType, superclassExclude); Map defaults = requireDefault ? generateDefaultFields(fields) : new HashMap(); - initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap()); + initialize(targetType, Object.class, fields, defaults, null, + new ConcurrentHashMap(), useStructureCompiler); } /** @@ -87,7 +110,8 @@ public class StructureModifier { */ protected void initialize(StructureModifier other) { initialize(other.targetType, other.fieldType, other.data, - other.defaultFields, other.converter, other.subtypeCache); + other.defaultFields, other.converter, other.subtypeCache, + other.useStructureCompiler); } /** @@ -102,12 +126,31 @@ public class StructureModifier { protected void initialize(Class targetType, Class fieldType, List data, Map defaultFields, EquivalentConverter converter, Map subTypeCache) { + initialize(targetType, fieldType, data, defaultFields, converter, subTypeCache, true); + } + + /** + * Initialize every field of this class. + * @param targetType - type of the object we're reading and writing from. + * @param fieldType - the common type of the fields we're modifying. + * @param data - list of fields to modify. + * @param defaultFields - list of fields that will be automatically initialized. + * @param converter - converts between the common field type and the actual type the consumer expects. + * @param subTypeCache - a structure modifier cache. + * @param useStructureModifier - whether or not to automatically compile this structure modifier. + */ + protected void initialize(Class targetType, Class fieldType, + List data, Map defaultFields, + EquivalentConverter converter, Map subTypeCache, + boolean useStructureCompiler) { + this.targetType = targetType; this.fieldType = fieldType; this.data = data; this.defaultFields = defaultFields; this.converter = converter; this.subtypeCache = subTypeCache; + this.useStructureCompiler = useStructureCompiler; } /** @@ -164,6 +207,40 @@ public class StructureModifier { return Modifier.isFinal(data.get(fieldIndex).getModifiers()); } + /** + * Set whether or not a field should be treated as read only. + *

+ * Note that changing the read-only state to TRUE will only work if the current + * field was recently read-only or the current structure modifier hasn't been compiled yet. + * + * @param fieldIndex - index of the field. + * @param value - TRUE if this field should be read only, FALSE otherwise. + * @throws FieldAccessException If we cannot modify the read-only status. + */ + public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException { + if (fieldIndex < 0 || fieldIndex >= data.size()) + new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")"); + + try { + StructureModifier.setFinalState(data.get(fieldIndex), value); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot write read only status due to a security limitation.", e); + } + } + + /** + * Alter the final status of a field. + * @param field - the field to change. + * @param isReadOnly - TRUE if the field should be read only, FALSE otherwise. + * @throws IllegalAccessException If an error occured. + */ + protected static void setFinalState(Field field, boolean isReadOnly) throws IllegalAccessException { + if (isReadOnly) + FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() | Modifier.FINAL, true); + else + FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() & ~Modifier.FINAL, true); + } + /** * Writes the value of a field given its index. * @param fieldIndex - index of the field. @@ -293,7 +370,7 @@ public class StructureModifier { subtypeCache.put(fieldType, result); // Automatically compile the structure modifier - if (BackgroundCompiler.getInstance() != null) + if (useStructureCompiler && BackgroundCompiler.getInstance() != null) BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType); } } @@ -365,7 +442,8 @@ public class StructureModifier { StructureModifier result = new StructureModifier(); result.initialize(targetType, fieldType, filtered, defaults, - converter, new ConcurrentHashMap()); + converter, new ConcurrentHashMap(), + useStructureCompiler); return result; } @@ -378,7 +456,7 @@ public class StructureModifier { StructureModifier copy = new StructureModifier(); // Create a new instance - copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache); + copy.initialize(this); copy.target = target; return copy; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java index 602815f8..8641a6a9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java @@ -19,10 +19,12 @@ package com.comphenix.protocol.reflect.compiler; import java.lang.reflect.Field; import java.util.Map; +import java.util.Set; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.google.common.collect.Sets; /** * Represents a compiled structure modifier. @@ -34,11 +36,33 @@ public abstract class CompiledStructureModifier extends StructureModifie // Used to compile instances of structure modifiers protected StructureCompiler compiler; + // Fields that originally were read only + private Set exempted; + public CompiledStructureModifier() { super(); customConvertHandling = true; } + @Override + public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException { + // We can remove the read-only status + if (isReadOnly(fieldIndex) && !value) { + if (exempted == null) + exempted = Sets.newHashSet(); + exempted.add(fieldIndex); + } + + // We can only make a certain kind of field read only + if (!isReadOnly(fieldIndex) && value) { + if (exempted == null || !exempted.contains(fieldIndex)) { + throw new IllegalStateException("Cannot make compiled field " + fieldIndex + " read only."); + } + } + + super.setReadOnly(fieldIndex, value); + } + // Speed up the default writer @SuppressWarnings("unchecked") @Override