Add the ability to write to final fields, even if it is compiled.
Dieser Commit ist enthalten in:
Ursprung
76d27017de
Commit
0b292af3b1
@ -30,7 +30,6 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
|||||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||||
import com.comphenix.protocol.utility.ChatExtensions;
|
import com.comphenix.protocol.utility.ChatExtensions;
|
||||||
import com.google.common.collect.DiscreteDomains;
|
import com.google.common.collect.DiscreteDomains;
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
import com.google.common.collect.Ranges;
|
import com.google.common.collect.Ranges;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
@ -66,8 +66,10 @@ public class ObjectCloner {
|
|||||||
// Copy every field
|
// Copy every field
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < modifierSource.size(); i++) {
|
for (int i = 0; i < modifierSource.size(); i++) {
|
||||||
|
if (!modifierDest.isReadOnly(i)) {
|
||||||
Object value = modifierSource.read(i);
|
Object value = modifierSource.read(i);
|
||||||
modifierDest.write(i, value);
|
modifierDest.write(i, value);
|
||||||
|
}
|
||||||
|
|
||||||
// System.out.println(String.format("Writing value %s to %s",
|
// System.out.println(String.format("Writing value %s to %s",
|
||||||
// value, modifier.getFields().get(i).getName()));
|
// value, modifier.getFields().get(i).getName()));
|
||||||
|
@ -61,6 +61,17 @@ public class StructureModifier<TField> {
|
|||||||
// Whether or subclasses should handle conversion
|
// Whether or subclasses should handle conversion
|
||||||
protected boolean customConvertHandling;
|
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.
|
* Creates a structure modifier.
|
||||||
* @param targetType - the structure to modify.
|
* @param targetType - the structure to modify.
|
||||||
@ -68,10 +79,22 @@ public class StructureModifier<TField> {
|
|||||||
* @param requireDefault - whether or not we will be using writeDefaults().
|
* @param requireDefault - whether or not we will be using writeDefaults().
|
||||||
*/
|
*/
|
||||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
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);
|
List<Field> fields = getFields(targetType, superclassExclude);
|
||||||
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
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) {
|
protected void initialize(StructureModifier<TField> other) {
|
||||||
initialize(other.targetType, other.fieldType, other.data,
|
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,
|
protected void initialize(Class targetType, Class fieldType,
|
||||||
List<Field> data, Map<Field, Integer> defaultFields,
|
List<Field> data, Map<Field, Integer> defaultFields,
|
||||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
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.targetType = targetType;
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.defaultFields = defaultFields;
|
this.defaultFields = defaultFields;
|
||||||
this.converter = converter;
|
this.converter = converter;
|
||||||
this.subtypeCache = subTypeCache;
|
this.subtypeCache = subTypeCache;
|
||||||
|
this.useStructureCompiler = useStructureCompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,6 +207,40 @@ public class StructureModifier<TField> {
|
|||||||
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
|
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.
|
* Writes the value of a field given its index.
|
||||||
* @param fieldIndex - index of the field.
|
* @param fieldIndex - index of the field.
|
||||||
@ -293,7 +370,7 @@ public class StructureModifier<TField> {
|
|||||||
subtypeCache.put(fieldType, result);
|
subtypeCache.put(fieldType, result);
|
||||||
|
|
||||||
// Automatically compile the structure modifier
|
// Automatically compile the structure modifier
|
||||||
if (BackgroundCompiler.getInstance() != null)
|
if (useStructureCompiler && BackgroundCompiler.getInstance() != null)
|
||||||
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,7 +442,8 @@ public class StructureModifier<TField> {
|
|||||||
|
|
||||||
StructureModifier<T> result = new StructureModifier<T>();
|
StructureModifier<T> result = new StructureModifier<T>();
|
||||||
result.initialize(targetType, fieldType, filtered, defaults,
|
result.initialize(targetType, fieldType, filtered, defaults,
|
||||||
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
converter, new ConcurrentHashMap<Class, StructureModifier>(),
|
||||||
|
useStructureCompiler);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +456,7 @@ public class StructureModifier<TField> {
|
|||||||
StructureModifier<TField> copy = new StructureModifier<TField>();
|
StructureModifier<TField> copy = new StructureModifier<TField>();
|
||||||
|
|
||||||
// Create a new instance
|
// Create a new instance
|
||||||
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
|
copy.initialize(this);
|
||||||
copy.target = target;
|
copy.target = target;
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,12 @@ package com.comphenix.protocol.reflect.compiler;
|
|||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a compiled structure modifier.
|
* Represents a compiled structure modifier.
|
||||||
@ -34,11 +36,33 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
|||||||
// Used to compile instances of structure modifiers
|
// Used to compile instances of structure modifiers
|
||||||
protected StructureCompiler compiler;
|
protected StructureCompiler compiler;
|
||||||
|
|
||||||
|
// Fields that originally were read only
|
||||||
|
private Set<Integer> exempted;
|
||||||
|
|
||||||
public CompiledStructureModifier() {
|
public CompiledStructureModifier() {
|
||||||
super();
|
super();
|
||||||
customConvertHandling = true;
|
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
|
// Speed up the default writer
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren