Add the ability to write to final fields, even if it is compiled.

This commit is contained in:
Kristian S. Stangeland 2012-11-11 02:09:45 +01:00
parent 76d27017de
commit 0b292af3b1
4 changed files with 111 additions and 8 deletions

View File

@ -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;

View File

@ -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()));

View File

@ -61,6 +61,17 @@ public class StructureModifier<TField> {
// 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<TField> {
* @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<Field> fields = getFields(targetType, superclassExclude);
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
initialize(targetType, Object.class, fields, defaults, null,
new ConcurrentHashMap<Class, StructureModifier>(), useStructureCompiler);
}
/**
@ -87,7 +110,8 @@ public class StructureModifier<TField> {
*/
protected void initialize(StructureModifier<TField> 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<TField> {
protected void initialize(Class targetType, Class fieldType,
List<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> 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<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> 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<TField> {
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
}
/**
* Set whether or not a field should be treated as read only.
* <p>
* 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<TField> {
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<TField> {
StructureModifier<T> result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults,
converter, new ConcurrentHashMap<Class, StructureModifier>());
converter, new ConcurrentHashMap<Class, StructureModifier>(),
useStructureCompiler);
return result;
}
@ -378,7 +456,7 @@ public class StructureModifier<TField> {
StructureModifier<TField> copy = new StructureModifier<TField>();
// Create a new instance
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
copy.initialize(this);
copy.target = target;
return copy;
}

View File

@ -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<TField> extends StructureModifie
// Used to compile instances of structure modifiers
protected StructureCompiler compiler;
// Fields that originally were read only
private Set<Integer> 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