2012-10-16 07:28:54 +02:00
|
|
|
/*
|
|
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
|
|
* Copyright (C) 2012 Kristian S. Stangeland
|
|
|
|
*
|
2014-11-29 04:08:46 +01:00
|
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
|
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
2012-10-16 07:28:54 +02:00
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*
|
2014-11-29 04:08:46 +01:00
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
2012-10-16 07:28:54 +02:00
|
|
|
* See the GNU General Public License for more details.
|
|
|
|
*
|
2014-11-29 04:08:46 +01:00
|
|
|
* You should have received a copy of the GNU General Public License along with this program;
|
|
|
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
2012-10-16 07:28:54 +02:00
|
|
|
* 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.comphenix.protocol.reflect;
|
|
|
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
import java.lang.reflect.Modifier;
|
2020-08-05 01:03:59 +02:00
|
|
|
import java.lang.reflect.ParameterizedType;
|
|
|
|
import java.lang.reflect.Type;
|
2017-08-04 20:01:06 +02:00
|
|
|
import java.util.*;
|
2012-10-16 07:28:54 +02:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2015-02-15 03:57:45 +01:00
|
|
|
import java.util.logging.Level;
|
2012-10-16 07:28:54 +02:00
|
|
|
|
2015-02-15 03:57:45 +01:00
|
|
|
import com.comphenix.protocol.ProtocolLibrary;
|
2016-04-02 20:47:54 +02:00
|
|
|
import com.comphenix.protocol.ProtocolLogger;
|
2015-02-15 03:57:45 +01:00
|
|
|
import com.comphenix.protocol.error.PluginContext;
|
2012-10-16 07:28:54 +02:00
|
|
|
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
2013-01-31 20:30:08 +01:00
|
|
|
import com.comphenix.protocol.reflect.instances.BannedGenerator;
|
2012-10-16 07:28:54 +02:00
|
|
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
2013-01-31 20:30:08 +01:00
|
|
|
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
|
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
2012-10-16 07:28:54 +02:00
|
|
|
import com.google.common.base.Function;
|
2013-01-27 15:35:39 +01:00
|
|
|
import com.google.common.base.Objects;
|
2012-10-16 07:28:54 +02:00
|
|
|
import com.google.common.collect.ImmutableList;
|
2013-01-31 20:30:08 +01:00
|
|
|
import com.google.common.collect.Lists;
|
2012-10-16 07:28:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides list-oriented access to the fields of a Minecraft packet.
|
|
|
|
* <p>
|
|
|
|
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
|
|
|
*
|
|
|
|
* @author Kristian
|
|
|
|
* @param <TField> Type of the fields to retrieve.
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
|
|
public class StructureModifier<TField> {
|
|
|
|
|
|
|
|
// Object and its type
|
|
|
|
protected Class targetType;
|
|
|
|
protected Object target;
|
|
|
|
|
|
|
|
// Converter. May be NULL.
|
|
|
|
protected EquivalentConverter<TField> converter;
|
|
|
|
|
|
|
|
// The fields to read in order
|
|
|
|
protected Class fieldType;
|
|
|
|
protected List<Field> data = new ArrayList<Field>();
|
|
|
|
|
|
|
|
// Improved default values
|
|
|
|
protected Map<Field, Integer> defaultFields;
|
|
|
|
|
|
|
|
// Cache of previous types
|
|
|
|
protected Map<Class, StructureModifier> subtypeCache;
|
|
|
|
|
|
|
|
// Whether or subclasses should handle conversion
|
|
|
|
protected boolean customConvertHandling;
|
|
|
|
|
2012-11-11 02:09:45 +01:00
|
|
|
// Whether or not to automatically compile the structure modifier
|
|
|
|
protected boolean useStructureCompiler;
|
2013-01-31 20:30:08 +01:00
|
|
|
|
|
|
|
// Instance generator we wil use
|
|
|
|
private static DefaultInstances DEFAULT_GENERATOR = getDefaultGenerator();
|
2012-11-11 02:09:45 +01:00
|
|
|
|
2013-01-31 20:30:08 +01:00
|
|
|
private static DefaultInstances getDefaultGenerator() {
|
|
|
|
List<InstanceProvider> providers = Lists.newArrayList();
|
|
|
|
|
|
|
|
// Prevent certain classes from being generated
|
|
|
|
providers.add(new BannedGenerator(MinecraftReflection.getItemStackClass(), MinecraftReflection.getBlockClass()));
|
|
|
|
providers.addAll(DefaultInstances.DEFAULT.getRegistered());
|
|
|
|
return DefaultInstances.fromCollection(providers);
|
|
|
|
}
|
2020-06-28 21:59:30 +02:00
|
|
|
|
2012-11-11 02:09:45 +01:00
|
|
|
/**
|
|
|
|
* Creates a structure modifier.
|
|
|
|
* @param targetType - the structure to modify.
|
|
|
|
*/
|
|
|
|
public StructureModifier(Class targetType) {
|
|
|
|
this(targetType, null, true);
|
|
|
|
}
|
|
|
|
|
2013-07-28 02:02:27 +02:00
|
|
|
/**
|
|
|
|
* Creates a structure modifier.
|
|
|
|
* @param targetType - the structure to modify.
|
|
|
|
* @param useStructureCompiler - whether or not to use a structure compiler.
|
|
|
|
*/
|
|
|
|
public StructureModifier(Class targetType, boolean useStructureCompiler) {
|
|
|
|
this(targetType, null, true, useStructureCompiler);
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* 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().
|
|
|
|
*/
|
|
|
|
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
2012-11-11 02:09:45 +01:00
|
|
|
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().
|
2012-11-13 17:50:36 +01:00
|
|
|
* @param useStructureCompiler - whether or not to automatically compile this structure modifier.
|
2012-11-11 02:09:45 +01:00
|
|
|
*/
|
|
|
|
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault, boolean useStructureCompiler) {
|
2012-10-16 07:28:54 +02:00
|
|
|
List<Field> fields = getFields(targetType, superclassExclude);
|
|
|
|
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
|
|
|
|
2014-11-29 04:08:46 +01:00
|
|
|
initialize(targetType, Object.class, fields, defaults, null,
|
2012-11-11 02:09:45 +01:00
|
|
|
new ConcurrentHashMap<Class, StructureModifier>(), useStructureCompiler);
|
2012-10-16 07:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Consumers of this method should call "initialize".
|
|
|
|
*/
|
|
|
|
protected StructureModifier() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize using the same field types.
|
|
|
|
* @param other - information to set.
|
|
|
|
*/
|
|
|
|
protected void initialize(StructureModifier<TField> other) {
|
2014-11-29 04:08:46 +01:00
|
|
|
initialize(other.targetType, other.fieldType, other.data,
|
|
|
|
other.defaultFields, other.converter, other.subtypeCache,
|
2012-11-11 02:09:45 +01:00
|
|
|
other.useStructureCompiler);
|
2012-10-16 07:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2014-11-29 04:08:46 +01:00
|
|
|
protected void initialize(Class targetType, Class fieldType,
|
2012-10-16 07:28:54 +02:00
|
|
|
List<Field> data, Map<Field, Integer> defaultFields,
|
|
|
|
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
2012-11-11 02:09:45 +01:00
|
|
|
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.
|
2012-11-13 17:50:36 +01:00
|
|
|
* @param useStructureCompiler - whether or not to automatically compile this structure modifier.
|
2012-11-11 02:09:45 +01:00
|
|
|
*/
|
2014-11-29 04:08:46 +01:00
|
|
|
protected void initialize(Class targetType, Class fieldType,
|
2012-11-11 02:09:45 +01:00
|
|
|
List<Field> data, Map<Field, Integer> defaultFields,
|
|
|
|
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache,
|
|
|
|
boolean useStructureCompiler) {
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
this.targetType = targetType;
|
|
|
|
this.fieldType = fieldType;
|
|
|
|
this.data = data;
|
|
|
|
this.defaultFields = defaultFields;
|
|
|
|
this.converter = converter;
|
|
|
|
this.subtypeCache = subTypeCache;
|
2012-11-11 02:09:45 +01:00
|
|
|
this.useStructureCompiler = useStructureCompiler;
|
2012-10-16 07:28:54 +02:00
|
|
|
}
|
2015-02-15 03:57:45 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* Reads the value of a field given its index.
|
2015-07-06 08:20:32 +02:00
|
|
|
* <p>
|
|
|
|
* Note: This method is prone to exceptions (there are currently 5 total throw statements). It is recommended that you
|
|
|
|
* use {@link #readSafely(int)}, which returns {@code null} if the field doesn't exist, instead of throwing an exception.
|
|
|
|
*
|
2012-10-16 07:28:54 +02:00
|
|
|
* @param fieldIndex - index of the field.
|
|
|
|
* @return Value of the field.
|
2015-07-06 08:20:32 +02:00
|
|
|
* @throws FieldAccessException if the field doesn't exist, or it cannot be accessed under the current security contraints.
|
2012-10-16 07:28:54 +02:00
|
|
|
*/
|
|
|
|
public TField read(int fieldIndex) throws FieldAccessException {
|
2015-02-15 03:57:45 +01:00
|
|
|
try {
|
|
|
|
return readInternal(fieldIndex);
|
|
|
|
} catch (FieldAccessException ex) {
|
|
|
|
String plugin = PluginContext.getPluginCaller(ex);
|
2015-07-06 08:20:32 +02:00
|
|
|
if (ProtocolLibrary.INCOMPATIBLE.contains(plugin)) {
|
2016-04-02 20:47:54 +02:00
|
|
|
ProtocolLogger.log(Level.WARNING, "Encountered an exception caused by incompatible plugin {0}.", plugin);
|
|
|
|
ProtocolLogger.log(Level.WARNING, "It is advised that you remove it.");
|
2015-02-15 03:57:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private TField readInternal(int fieldIndex) throws FieldAccessException {
|
2015-01-29 03:28:28 +01:00
|
|
|
if (target == null)
|
|
|
|
throw new IllegalStateException("Cannot read from a null target!");
|
|
|
|
|
2014-11-29 04:08:46 +01:00
|
|
|
if (fieldIndex < 0)
|
|
|
|
throw new FieldAccessException(String.format("Field index (%s) cannot be negative.", fieldIndex));
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2015-01-29 03:30:17 +01:00
|
|
|
if (data.size() == 0)
|
2015-01-31 06:54:55 +01:00
|
|
|
throw new FieldAccessException(String.format("No field with type %s exists in class %s.", fieldType.getName(),
|
|
|
|
target.getClass().getSimpleName()));
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2014-11-29 04:08:46 +01:00
|
|
|
if (fieldIndex >= data.size())
|
|
|
|
throw new FieldAccessException(String.format("Field index out of bounds. (Index: %s, Size: %s)", fieldIndex, data.size()));
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
try {
|
|
|
|
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
2014-11-29 04:08:46 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
// Use the converter, if we have it
|
2015-01-29 03:28:28 +01:00
|
|
|
if (needConversion()) {
|
2012-10-16 07:28:54 +02:00
|
|
|
return converter.getSpecific(result);
|
2015-01-29 03:28:28 +01:00
|
|
|
} else {
|
2012-10-16 07:28:54 +02:00
|
|
|
return (TField) result;
|
2015-01-29 03:28:28 +01:00
|
|
|
}
|
2012-10-16 07:28:54 +02:00
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
|
|
|
}
|
|
|
|
}
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
2015-07-06 08:20:32 +02:00
|
|
|
* Reads the value of a field only if it exists. If the field does not exist, {@code null} is returned.
|
|
|
|
* <p>
|
|
|
|
* As its name implies, this method is a much safer alternative to {@link #read(int)}.
|
|
|
|
* In addition to throwing less exceptions and thereby causing less console spam, this
|
|
|
|
* method makes providing backwards compatiblity signficiantly easier, as shown below:
|
|
|
|
*
|
|
|
|
* <pre><code>
|
|
|
|
* BlockPosition position = packet.getBlockPositionModifier().readSafely(0);
|
|
|
|
* if (position != null) {
|
|
|
|
* // Handle 1.8+
|
|
|
|
* } else {
|
|
|
|
* // Handle 1.7-
|
|
|
|
* }
|
|
|
|
* </code></pre>
|
|
|
|
*
|
2012-10-16 07:28:54 +02:00
|
|
|
* @param fieldIndex - index of the field.
|
|
|
|
* @return Value of the field, or NULL if it doesn't exist.
|
2015-07-06 08:20:32 +02:00
|
|
|
* @throws FieldAccessException if the field cannot be accessed under the current security constraints.
|
2012-10-16 07:28:54 +02:00
|
|
|
*/
|
|
|
|
public TField readSafely(int fieldIndex) throws FieldAccessException {
|
|
|
|
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
|
|
|
return read(fieldIndex);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2017-08-04 20:01:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the value of a field only if it exists. If the field does not exist, an empty {@link Optional} is returned.
|
|
|
|
* <p>
|
|
|
|
* This method has the same functionality as {@link #readSafely(int)}, but enforces null checks by way of an Optional.
|
|
|
|
* It will eventually become the preferred method of reading fields.
|
|
|
|
*
|
|
|
|
* @param fieldIndex index of the field
|
|
|
|
* @return An optional that may contain the value of the field
|
|
|
|
* @see #readSafely(int)
|
|
|
|
*/
|
|
|
|
public Optional<TField> optionRead(int fieldIndex) {
|
|
|
|
try {
|
|
|
|
return Optional.ofNullable(read(fieldIndex));
|
|
|
|
} catch (FieldAccessException ex) {
|
|
|
|
return Optional.empty();
|
|
|
|
}
|
|
|
|
}
|
2012-10-16 07:28:54 +02:00
|
|
|
|
2012-11-11 01:22:17 +01:00
|
|
|
/**
|
|
|
|
* Determine whether or not a field is read-only (final).
|
|
|
|
* @param fieldIndex - index of the field.
|
|
|
|
* @return TRUE if the field by the given index is read-only, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
public boolean isReadOnly(int fieldIndex) {
|
2012-12-27 09:23:07 +01:00
|
|
|
return Modifier.isFinal(getField(fieldIndex).getModifiers());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if a given field is public or not.
|
|
|
|
* @param fieldIndex - field index.
|
|
|
|
* @return TRUE if the field is public, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
public boolean isPublic(int fieldIndex) {
|
|
|
|
return Modifier.isPublic(getField(fieldIndex).getModifiers());
|
2012-11-11 01:22:17 +01:00
|
|
|
}
|
|
|
|
|
2012-11-11 02:09:45 +01:00
|
|
|
/**
|
|
|
|
* 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())
|
2012-11-20 06:46:21 +01:00
|
|
|
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
2012-11-11 02:09:45 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* Writes the value of a field given its index.
|
|
|
|
* @param fieldIndex - index of the field.
|
|
|
|
* @param value - new value of the field.
|
|
|
|
* @return This structure modifier - for chaining.
|
|
|
|
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
|
|
|
*/
|
|
|
|
public StructureModifier<TField> write(int fieldIndex, TField value) throws FieldAccessException {
|
2015-02-15 03:57:45 +01:00
|
|
|
try {
|
|
|
|
return writeInternal(fieldIndex, value);
|
|
|
|
} catch (FieldAccessException ex) {
|
|
|
|
String plugin = PluginContext.getPluginCaller(ex);
|
2015-07-06 08:20:32 +02:00
|
|
|
if (ProtocolLibrary.INCOMPATIBLE.contains(plugin)) {
|
2016-04-02 20:47:54 +02:00
|
|
|
ProtocolLogger.log(Level.WARNING, "Encountered an exception caused by incompatible plugin {0}.", plugin);
|
|
|
|
ProtocolLogger.log(Level.WARNING, "It is advised that you remove it.");
|
2015-02-15 03:57:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private StructureModifier<TField> writeInternal(int fieldIndex, TField value) throws FieldAccessException {
|
2015-01-29 03:28:28 +01:00
|
|
|
if (target == null)
|
|
|
|
throw new IllegalStateException("Cannot read from a null target!");
|
|
|
|
|
2014-12-01 00:29:22 +01:00
|
|
|
if (fieldIndex < 0)
|
|
|
|
throw new FieldAccessException(String.format("Field index (%s) cannot be negative.", fieldIndex));
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2015-01-29 03:30:17 +01:00
|
|
|
if (data.size() == 0)
|
2015-01-31 06:54:55 +01:00
|
|
|
throw new FieldAccessException(String.format("No field with type %s exists in class %s.", fieldType.getName(),
|
|
|
|
target.getClass().getSimpleName()));
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2014-12-01 00:29:22 +01:00
|
|
|
if (fieldIndex >= data.size())
|
|
|
|
throw new FieldAccessException(String.format("Field index out of bounds. (Index: %s, Size: %s)", fieldIndex, data.size()));
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
// Use the converter, if it exists
|
2017-07-24 20:15:56 +02:00
|
|
|
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
try {
|
|
|
|
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
|
|
|
}
|
2015-01-29 03:28:28 +01:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
// Make this method chainable
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2012-11-13 14:34:07 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the type of a specified field.
|
|
|
|
* @param index - the index.
|
|
|
|
* @return The type of the given field.
|
|
|
|
*/
|
|
|
|
protected Class<?> getFieldType(int index) {
|
|
|
|
return data.get(index).getType();
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* Whether or not we should use the converter instance.
|
|
|
|
* @return TRUE if we should, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
private final boolean needConversion() {
|
|
|
|
return converter != null && !customConvertHandling;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes the value of a given field IF and ONLY if it exists.
|
|
|
|
* @param fieldIndex - index of the potential field.
|
|
|
|
* @param value - new value of the field.
|
|
|
|
* @return This structure modifer - for chaining.
|
|
|
|
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
|
|
|
*/
|
|
|
|
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws FieldAccessException {
|
|
|
|
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
|
|
|
write(fieldIndex, value);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Correctly modifies the value of a field.
|
|
|
|
* @param fieldIndex - index of the field to modify.
|
|
|
|
* @param select - the function that modifies the field value.
|
|
|
|
* @return This structure modifier - for chaining.
|
|
|
|
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
|
|
|
*/
|
|
|
|
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws FieldAccessException {
|
|
|
|
TField value = read(fieldIndex);
|
|
|
|
return write(fieldIndex, select.apply(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @param <T> Type
|
2012-10-16 07:28:54 +02:00
|
|
|
* @param fieldType - the type, or supertype, of every field to modify.
|
|
|
|
* @return A structure modifier for fields of this type.
|
|
|
|
*/
|
|
|
|
public <T> StructureModifier<T> withType(Class fieldType) {
|
|
|
|
return withType(fieldType, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}.
|
|
|
|
* @return The current structure modifier - for chaining.
|
|
|
|
* @throws FieldAccessException If we're unable to write to the fields due to a security limitation.
|
|
|
|
*/
|
|
|
|
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
|
|
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
2015-07-07 03:08:14 +02:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
// Write a default instance to every field
|
|
|
|
for (Field field : defaultFields.keySet()) {
|
|
|
|
try {
|
2015-07-07 19:55:31 +02:00
|
|
|
// Special case for Spigot's custom chat components
|
|
|
|
// They must be null or messages will be blank
|
2015-07-07 03:08:14 +02:00
|
|
|
if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) {
|
|
|
|
FieldUtils.writeField(field, target, null, true);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
FieldUtils.writeField(field, target, generator.getDefault(field.getType()), true);
|
2012-10-16 07:28:54 +02:00
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
|
|
|
|
}
|
|
|
|
}
|
2015-07-07 03:08:14 +02:00
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
return this;
|
|
|
|
}
|
2020-08-05 01:03:59 +02:00
|
|
|
|
|
|
|
private static Type[] getParamTypes(Field field) {
|
|
|
|
Type genericType = field.getGenericType();
|
|
|
|
if (genericType instanceof ParameterizedType) {
|
|
|
|
return ((ParameterizedType) genericType).getActualTypeArguments();
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Class<?>[0];
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @param <T> Type
|
2012-10-16 07:28:54 +02:00
|
|
|
* @param fieldType - the type, or supertype, of every field to modify.
|
|
|
|
* @param converter - converts objects into the given type.
|
|
|
|
* @return A structure modifier for fields of this type.
|
|
|
|
*/
|
2020-08-24 17:40:47 +02:00
|
|
|
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
|
|
|
|
return withParamType(fieldType, converter);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
|
|
|
* @param <T> Type
|
|
|
|
* @param fieldType - the type, or supertype, of every field to modify.
|
|
|
|
* @param converter - converts objects into the given type.
|
|
|
|
* @param paramTypes - field type parameters
|
|
|
|
* @return A structure modifier for fields of this type.
|
|
|
|
*/
|
2012-10-16 07:28:54 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
2020-08-24 17:40:47 +02:00
|
|
|
public <T> StructureModifier<T> withParamType(Class fieldType, EquivalentConverter<T> converter, Class... paramTypes) {
|
2017-05-24 00:52:28 +02:00
|
|
|
if (fieldType == null) {
|
|
|
|
// It's not supported in this version, so return an empty modifier
|
|
|
|
return new StructureModifier<T>() {
|
|
|
|
@Override
|
|
|
|
public T read(int index) { return null; }
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public StructureModifier<T> write(int index, T value) { return this; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
StructureModifier<T> result = subtypeCache.get(fieldType);
|
|
|
|
|
|
|
|
// Do we need to update the cache?
|
|
|
|
if (result == null) {
|
|
|
|
List<Field> filtered = new ArrayList<Field>();
|
|
|
|
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
for (Field field : data) {
|
2017-05-24 00:52:28 +02:00
|
|
|
if (fieldType.isAssignableFrom(field.getType())) {
|
2020-08-05 01:03:59 +02:00
|
|
|
if (paramTypes.length == 0 || Arrays.equals(getParamTypes(field), paramTypes)) {
|
|
|
|
filtered.add(field);
|
|
|
|
|
|
|
|
// Don't use the original index
|
|
|
|
if (defaultFields.containsKey(field))
|
|
|
|
defaults.put(field, index);
|
|
|
|
}
|
2012-10-16 07:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Keep track of the field index
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache structure modifiers
|
2014-06-10 23:01:24 +02:00
|
|
|
result = withFieldType(fieldType, filtered, defaults);
|
2017-05-24 00:52:28 +02:00
|
|
|
|
|
|
|
subtypeCache.put(fieldType, result);
|
2012-10-16 07:28:54 +02:00
|
|
|
|
2017-05-24 00:52:28 +02:00
|
|
|
// Automatically compile the structure modifier
|
|
|
|
if (useStructureCompiler && BackgroundCompiler.getInstance() != null)
|
|
|
|
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
2012-10-16 07:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the target too
|
|
|
|
result = result.withTarget(target);
|
|
|
|
|
|
|
|
// And the converter, if it's needed
|
2013-01-27 15:35:39 +01:00
|
|
|
if (!Objects.equal(result.converter, converter)) {
|
2012-10-16 07:28:54 +02:00
|
|
|
result = result.withConverter(converter);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the common type of each field.
|
|
|
|
* @return Common type of each field.
|
|
|
|
*/
|
|
|
|
public Class getFieldType() {
|
|
|
|
return fieldType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the type of the object we're modifying.
|
|
|
|
* @return Type of the object.
|
|
|
|
*/
|
|
|
|
public Class getTargetType() {
|
|
|
|
return targetType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the object we're currently modifying.
|
|
|
|
* @return Object we're modifying.
|
|
|
|
*/
|
|
|
|
public Object getTarget() {
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the number of readable types.
|
|
|
|
* @return Readable types.
|
|
|
|
*/
|
|
|
|
public int size() {
|
|
|
|
return data.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new structure modifier for the new field type.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @param <T> Type
|
2012-10-16 07:28:54 +02:00
|
|
|
* @param fieldType - common type of each field.
|
|
|
|
* @param filtered - list of fields after filtering the original modifier.
|
|
|
|
* @param defaults - list of default values after filtering the original.
|
2014-06-10 23:01:24 +02:00
|
|
|
* @return A new structure modifier.
|
|
|
|
*/
|
|
|
|
protected <T> StructureModifier<T> withFieldType(
|
|
|
|
Class fieldType, List<Field> filtered, Map<Field, Integer> defaults) {
|
|
|
|
return withFieldType(fieldType, filtered, defaults, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new structure modifier for the new field type.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @param <T> Type
|
2014-06-10 23:01:24 +02:00
|
|
|
* @param fieldType - common type of each field.
|
|
|
|
* @param filtered - list of fields after filtering the original modifier.
|
|
|
|
* @param defaults - list of default values after filtering the original.
|
|
|
|
* @param converter - the new converter, or NULL.
|
2012-10-16 07:28:54 +02:00
|
|
|
* @return A new structure modifier.
|
|
|
|
*/
|
|
|
|
protected <T> StructureModifier<T> withFieldType(
|
2014-11-29 04:08:46 +01:00
|
|
|
Class fieldType, List<Field> filtered,
|
2012-10-16 07:28:54 +02:00
|
|
|
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
|
|
|
|
|
|
|
|
StructureModifier<T> result = new StructureModifier<T>();
|
2014-11-29 04:08:46 +01:00
|
|
|
result.initialize(targetType, fieldType, filtered, defaults,
|
|
|
|
converter, new ConcurrentHashMap<Class, StructureModifier>(),
|
2012-11-11 02:09:45 +01:00
|
|
|
useStructureCompiler);
|
2012-10-16 07:28:54 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a structure modifier of the same type for a different object target.
|
|
|
|
* @param target - different target of the same type.
|
|
|
|
* @return Structure modifier with the new target.
|
|
|
|
*/
|
|
|
|
public StructureModifier<TField> withTarget(Object target) {
|
|
|
|
StructureModifier<TField> copy = new StructureModifier<TField>();
|
|
|
|
|
|
|
|
// Create a new instance
|
2012-11-11 02:09:45 +01:00
|
|
|
copy.initialize(this);
|
2012-10-16 07:28:54 +02:00
|
|
|
copy.target = target;
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a structure modifier with the same type and target, but using a new object converter.
|
|
|
|
* @param converter - the object converter to use.
|
|
|
|
* @return Structure modifier with the new converter.
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
|
|
|
|
StructureModifier copy = withTarget(target);
|
|
|
|
|
|
|
|
copy.setConverter(converter);
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the current object converter. Should only be called during construction.
|
|
|
|
* @param converter - current object converter.
|
|
|
|
*/
|
|
|
|
protected void setConverter(EquivalentConverter<TField> converter) {
|
|
|
|
this.converter = converter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
|
|
|
* @return List of fields.
|
|
|
|
*/
|
|
|
|
public List<Field> getFields() {
|
|
|
|
return ImmutableList.copyOf(data);
|
|
|
|
}
|
|
|
|
|
2012-12-27 09:23:07 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a field by index.
|
|
|
|
* @param fieldIndex - index of the field to retrieve.
|
|
|
|
* @return The field represented with the given index.
|
|
|
|
* @throws IllegalArgumentException If no field with the given index can be found.
|
|
|
|
*/
|
|
|
|
public Field getField(int fieldIndex) {
|
|
|
|
if (fieldIndex < 0 || fieldIndex >= data.size())
|
|
|
|
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
|
|
|
|
|
|
|
return data.get(fieldIndex);
|
|
|
|
}
|
|
|
|
|
2012-10-16 07:28:54 +02:00
|
|
|
/**
|
|
|
|
* Retrieve every value stored in the fields of the current type.
|
|
|
|
* @return Every field value.
|
|
|
|
* @throws FieldAccessException Unable to access one or all of the fields
|
|
|
|
*/
|
|
|
|
public List<TField> getValues() throws FieldAccessException {
|
|
|
|
List<TField> values = new ArrayList<TField>();
|
|
|
|
|
|
|
|
for (int i = 0; i < size(); i++) {
|
|
|
|
values.add(read(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to generate plausible default values
|
|
|
|
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
|
|
|
|
|
|
|
|
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
|
2013-01-31 20:30:08 +01:00
|
|
|
DefaultInstances generator = DEFAULT_GENERATOR;
|
2012-10-16 07:28:54 +02:00
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
for (Field field : fields) {
|
|
|
|
Class<?> type = field.getType();
|
2012-11-11 01:22:17 +01:00
|
|
|
int modifier = field.getModifiers();
|
2012-10-16 07:28:54 +02:00
|
|
|
|
2012-11-11 01:22:17 +01:00
|
|
|
// First, ignore primitive fields and final fields
|
|
|
|
if (!type.isPrimitive() && !Modifier.isFinal(modifier)) {
|
2012-10-16 07:28:54 +02:00
|
|
|
// Next, see if we actually can generate a default value
|
|
|
|
if (generator.getDefault(type) != null) {
|
|
|
|
// If so, require it
|
|
|
|
requireDefaults.put(field, index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increment field index
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return requireDefaults;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to filter out irrelevant fields
|
|
|
|
private static List<Field> getFields(Class type, Class superclassExclude) {
|
|
|
|
List<Field> result = new ArrayList<Field>();
|
|
|
|
|
|
|
|
// Retrieve every private and public field
|
2013-12-04 04:17:02 +01:00
|
|
|
for (Field field : FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude)) {
|
2012-10-16 07:28:54 +02:00
|
|
|
int mod = field.getModifiers();
|
|
|
|
|
2012-11-11 01:22:17 +01:00
|
|
|
// Ignore static and "abstract packet" fields
|
2014-11-29 04:08:46 +01:00
|
|
|
if (!Modifier.isStatic(mod) &&
|
2012-11-11 01:22:17 +01:00
|
|
|
(superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
2012-10-16 07:28:54 +02:00
|
|
|
)) {
|
|
|
|
|
|
|
|
result.add(field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2015-06-19 19:14:20 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "StructureModifier[fieldType=" + fieldType + ", data=" + data + "]";
|
|
|
|
}
|
2016-05-21 23:37:13 +02:00
|
|
|
}
|