690 lines
25 KiB
Java
690 lines
25 KiB
Java
/*
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
* Copyright (C) 2012 Kristian S. Stangeland
|
|
*
|
|
* 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
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* 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.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* 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
|
|
* 02111-1307 USA
|
|
*/
|
|
|
|
package com.comphenix.protocol.reflect;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
|
import com.comphenix.protocol.reflect.instances.BannedGenerator;
|
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
|
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
import com.google.common.collect.HashBasedTable;
|
|
import com.google.common.collect.Table;
|
|
|
|
import java.lang.Iterable;
|
|
import java.util.Iterator;
|
|
import java.lang.ref.Reference;
|
|
import java.lang.ref.SoftReference;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Modifier;
|
|
import java.lang.reflect.ParameterizedType;
|
|
import java.lang.reflect.Type;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Spliterator;
|
|
import java.util.Spliterators;
|
|
import java.util.function.UnaryOperator;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
/**
|
|
* Provides list-oriented access to the fields of a Minecraft packet.
|
|
* <p>
|
|
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
|
*
|
|
* @param <T> Type of the fields to retrieve.
|
|
* @author Kristian
|
|
*/
|
|
public class StructureModifier<T> implements Iterable<StructureModifierIntermediate<T>> {
|
|
|
|
// Instance generator we will use
|
|
private static final DefaultInstances DEFAULT_GENERATOR = getDefaultGenerator();
|
|
// a structure modifier which does nothing
|
|
private static final StructureModifier<Object> NO_OP_MODIFIER = new StructureModifier<Object>() {
|
|
@Override
|
|
public Object read(int fieldIndex) throws FieldAccessException {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public StructureModifier<Object> write(int fieldIndex, Object value) throws FieldAccessException {
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
protected FieldAccessor findFieldAccessor(int fieldIndex) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Object and its type
|
|
protected Object target;
|
|
protected Class<?> targetType;
|
|
|
|
// The fields to read in order
|
|
protected Class<?> fieldType;
|
|
protected List<FieldAccessor> accessors = new ArrayList<>();
|
|
|
|
// Converter. May be NULL.
|
|
protected EquivalentConverter<T> converter;
|
|
|
|
// Improved default values
|
|
protected Map<FieldAccessor, Integer> defaultFields;
|
|
// Cache of previous types
|
|
protected Map<Class<?>, StructureModifier<?>> subtypeCache;
|
|
|
|
// Whether or subclasses should handle conversion
|
|
protected boolean customConvertHandling;
|
|
|
|
/**
|
|
* Creates a structure modifier.
|
|
*
|
|
* @param targetType - the structure to modify.
|
|
*/
|
|
public StructureModifier(Class<?> targetType) {
|
|
this(targetType, Object.class, true);
|
|
}
|
|
|
|
/**
|
|
* Creates a structure modifier.
|
|
*
|
|
* @param targetType - the structure to modify.
|
|
* @param superclassExclude - a superclass to exclude.
|
|
* @param requireDefault - whether we will be using writeDefaults()
|
|
*/
|
|
public StructureModifier(
|
|
Class<?> targetType,
|
|
Class<?> superclassExclude,
|
|
boolean requireDefault
|
|
) {
|
|
List<FieldAccessor> fields = getFields(targetType, superclassExclude);
|
|
Map<FieldAccessor, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<>();
|
|
|
|
this.initialize(targetType, Object.class, fields, defaults, null, new HashMap<>());
|
|
}
|
|
|
|
/**
|
|
* Consumers of this method should call "initialize".
|
|
*/
|
|
protected StructureModifier() {
|
|
|
|
}
|
|
|
|
private static DefaultInstances getDefaultGenerator() {
|
|
List<InstanceProvider> providers = new ArrayList<>();
|
|
|
|
// Prevent certain classes from being generated
|
|
providers.add(new BannedGenerator(MinecraftReflection.getItemStackClass(), MinecraftReflection.getBlockClass()));
|
|
providers.addAll(DefaultInstances.DEFAULT.getRegistered());
|
|
return DefaultInstances.fromCollection(providers);
|
|
}
|
|
|
|
// Used to generate plausible default values
|
|
private static Map<FieldAccessor, Integer> generateDefaultFields(Collection<FieldAccessor> fields) {
|
|
int currentFieldIndex = 0;
|
|
Map<FieldAccessor, Integer> requireDefaults = new HashMap<>();
|
|
|
|
for (FieldAccessor accessor : fields) {
|
|
Field field = accessor.getField();
|
|
if (!field.getType().isPrimitive() && !Modifier.isFinal(field.getModifiers())) {
|
|
if (DEFAULT_GENERATOR.hasDefault(field.getType())) {
|
|
requireDefaults.put(accessor, currentFieldIndex);
|
|
}
|
|
}
|
|
|
|
// increment the index of the processed fields
|
|
currentFieldIndex++;
|
|
}
|
|
|
|
return requireDefaults;
|
|
}
|
|
|
|
// Used to filter out irrelevant fields
|
|
private static final ThreadLocal<Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>>>
|
|
fieldCacheLocal = ThreadLocal.withInitial(HashBasedTable::create);
|
|
private static final Class<?> NULL_CACHE_CLASS_REPLACEMENT = Void.class;
|
|
|
|
// Used to filter out irrelevant fields
|
|
private static List<FieldAccessor> getFields(Class<?> type, Class<?> superclassExclude) {
|
|
if (type == null) {
|
|
throw new IllegalArgumentException("Type cannot be NULL.");
|
|
}
|
|
Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>> fieldCache = fieldCacheLocal.get();
|
|
Class<?> superclassKey = superclassExclude == null ? NULL_CACHE_CLASS_REPLACEMENT : superclassExclude;
|
|
Reference<List<FieldAccessor>> cacheEntryReference = fieldCache.get(type, superclassKey);
|
|
if (cacheEntryReference != null) {
|
|
List<FieldAccessor> cacheEntry = cacheEntryReference.get();
|
|
if (cacheEntry != null) {
|
|
return cacheEntry;
|
|
}
|
|
}
|
|
List<FieldAccessor> accessors = FuzzyReflection.fromClass(type, true)
|
|
.getDeclaredFields(superclassExclude)
|
|
.stream()
|
|
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
|
.map(Accessors::getFieldAccessor)
|
|
.collect(Collectors.toList());
|
|
fieldCache.put(type, superclassKey, new SoftReference<>(accessors));
|
|
fieldCache.cellSet().removeIf(entry -> entry.getValue().get() == null);
|
|
return accessors;
|
|
}
|
|
|
|
/**
|
|
* Initialize using the same field types.
|
|
*
|
|
* @param other - information to set.
|
|
*/
|
|
protected void initialize(StructureModifier<T> other) {
|
|
this.initialize(
|
|
other.targetType,
|
|
other.fieldType,
|
|
other.accessors,
|
|
other.defaultFields,
|
|
other.converter,
|
|
other.subtypeCache);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
protected void initialize(
|
|
Class<?> targetType,
|
|
Class<?> fieldType,
|
|
List<FieldAccessor> data,
|
|
Map<FieldAccessor, Integer> defaultFields,
|
|
EquivalentConverter<T> converter,
|
|
Map<Class<?>, StructureModifier<?>> subTypeCache
|
|
) {
|
|
this.targetType = targetType;
|
|
this.fieldType = fieldType;
|
|
this.accessors = data;
|
|
this.defaultFields = defaultFields;
|
|
this.converter = converter;
|
|
this.subtypeCache = subTypeCache;
|
|
}
|
|
|
|
/**
|
|
* Reads the value of a field given its index.
|
|
* <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.
|
|
*
|
|
* @param fieldIndex - index of the field.
|
|
* @return Value of the field.
|
|
* @throws FieldAccessException if the given field index is out of bounds.
|
|
* @throws IllegalStateException if this modifier has no target set.
|
|
*/
|
|
public T read(int fieldIndex) throws FieldAccessException {
|
|
FieldAccessor accessor = this.findFieldAccessor(fieldIndex);
|
|
if (accessor == null) {
|
|
throw FieldAccessException.fromFormat(
|
|
"Field index %d is out of bounds for length %s",
|
|
fieldIndex,
|
|
this.accessors.size());
|
|
}
|
|
|
|
return this.readInternal(accessor);
|
|
}
|
|
|
|
/**
|
|
* 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>
|
|
*
|
|
* @param fieldIndex - index of the field.
|
|
* @return Value of the field, or NULL if it doesn't exist.
|
|
* @throws IllegalStateException if this modifier has no target set.
|
|
*/
|
|
public T readSafely(int fieldIndex) throws FieldAccessException {
|
|
return this.readInternal(this.findFieldAccessor(fieldIndex));
|
|
}
|
|
|
|
/**
|
|
* 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<T> optionRead(int fieldIndex) {
|
|
return Optional.ofNullable(this.readSafely(fieldIndex));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private T readInternal(FieldAccessor accessor) {
|
|
// just return null if the accessor is null
|
|
if (accessor == null) {
|
|
return null;
|
|
}
|
|
|
|
// get the field value and convert it if needed
|
|
Object fieldValue = accessor.get(this.target);
|
|
return this.needConversion() ? this.converter.getSpecific(fieldValue) : (T) fieldValue;
|
|
}
|
|
|
|
/**
|
|
* 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<T> write(int fieldIndex, T value) throws FieldAccessException {
|
|
FieldAccessor accessor = this.findFieldAccessor(fieldIndex);
|
|
if (accessor == null) {
|
|
throw FieldAccessException.fromFormat(
|
|
"Field index %d is out of bounds for length %s",
|
|
fieldIndex,
|
|
this.accessors.size());
|
|
}
|
|
|
|
return this.writeInternal(accessor, value);
|
|
}
|
|
|
|
/**
|
|
* 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<T> writeSafely(int fieldIndex, T value) throws FieldAccessException {
|
|
FieldAccessor accessor = this.findFieldAccessor(fieldIndex);
|
|
return this.writeInternal(accessor, value);
|
|
}
|
|
|
|
/**
|
|
* 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<T> modify(int fieldIndex, UnaryOperator<T> select) throws FieldAccessException {
|
|
T value = this.read(fieldIndex);
|
|
return this.write(fieldIndex, select.apply(value));
|
|
}
|
|
|
|
private StructureModifier<T> writeInternal(FieldAccessor accessor, T value) throws FieldAccessException {
|
|
// just ignore if the accessor is not present
|
|
if (accessor == null) {
|
|
return this;
|
|
}
|
|
|
|
// convert and write
|
|
Object fieldValue = this.needConversion() ? this.converter.getGeneric(value) : value;
|
|
accessor.set(this.target, fieldValue);
|
|
|
|
return this;
|
|
}
|
|
|
|
protected FieldAccessor findFieldAccessor(int fieldIndex) {
|
|
if (this.target == null) {
|
|
throw new IllegalStateException("Cannot read from modifier which has no target!");
|
|
}
|
|
|
|
// check if the field is out of bounds
|
|
if (fieldIndex < 0 || fieldIndex >= this.accessors.size()) {
|
|
return null;
|
|
}
|
|
|
|
return this.accessors.get(fieldIndex);
|
|
}
|
|
|
|
/**
|
|
* Whether we should use the converter instance.
|
|
*
|
|
* @return TRUE if we should, FALSE otherwise.
|
|
*/
|
|
private boolean needConversion() {
|
|
return this.converter != null && !this.customConvertHandling;
|
|
}
|
|
|
|
/**
|
|
* 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<T> writeDefaults() throws FieldAccessException {
|
|
// Write a default instance to every field
|
|
for (FieldAccessor accessor : this.defaultFields.keySet()) {
|
|
// Special case for Spigot's custom chat components
|
|
// They must be null or messages will be blank
|
|
Field field = accessor.getField();
|
|
if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) {
|
|
accessor.set(this.target, null);
|
|
continue;
|
|
}
|
|
|
|
// get the default value and write the field
|
|
Object defaultValue = DEFAULT_GENERATOR.getDefault(field.getType());
|
|
accessor.set(this.target, defaultValue);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the common type of each field.
|
|
*
|
|
* @return Common type of each field.
|
|
*/
|
|
public Class<?> getFieldType() {
|
|
return this.fieldType;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the type of the object we're modifying.
|
|
*
|
|
* @return Type of the object.
|
|
*/
|
|
public Class<?> getTargetType() {
|
|
return this.targetType;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the object we're currently modifying.
|
|
*
|
|
* @return Object we're modifying.
|
|
*/
|
|
public Object getTarget() {
|
|
return this.target;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the number of readable types.
|
|
*
|
|
* @return Readable types.
|
|
*/
|
|
public int size() {
|
|
return this.accessors.size();
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
|
*
|
|
* @return List of fields.
|
|
*/
|
|
public List<FieldAccessor> getFields() {
|
|
return Collections.unmodifiableList(this.accessors);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
FieldAccessor accessor = this.findFieldAccessor(fieldIndex);
|
|
if (accessor == null) {
|
|
throw new IllegalArgumentException(String.format(
|
|
"Field index %d is out of bounds for length %s",
|
|
fieldIndex,
|
|
this.accessors.size()));
|
|
}
|
|
|
|
return accessor.getField();
|
|
}
|
|
|
|
/**
|
|
* 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<T> getValues() throws FieldAccessException {
|
|
List<T> values = new ArrayList<>();
|
|
for (int i = 0; i < this.size(); i++) {
|
|
values.add(this.readSafely(i));
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
|
*
|
|
* @param <R> Type
|
|
* @param fieldType - the type, or supertype, of every field to modify.
|
|
* @return A structure modifier for fields of this type.
|
|
*/
|
|
public <R> StructureModifier<R> withType(Class<?> fieldType) {
|
|
return this.withType(fieldType, null);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
|
*
|
|
* @param <R> Type
|
|
* @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.
|
|
*/
|
|
public <R> StructureModifier<R> withType(Class<?> fieldType, EquivalentConverter<R> converter) {
|
|
return this.withParamType(fieldType, converter);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
|
*
|
|
* @param <R> 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.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public <R> StructureModifier<R> withParamType(
|
|
Class<?> fieldType,
|
|
EquivalentConverter<R> converter,
|
|
Class<?>... paramTypes
|
|
) {
|
|
if (fieldType == null) {
|
|
// It's not supported in this version, so return an empty modifier
|
|
return (StructureModifier<R>) NO_OP_MODIFIER;
|
|
}
|
|
|
|
// Do we need to update the cache?
|
|
StructureModifier<R> result = (StructureModifier<R>) this.subtypeCache.get(fieldType);
|
|
if (result == null) {
|
|
List<FieldAccessor> fields = new ArrayList<>();
|
|
Map<FieldAccessor, Integer> defaults = new HashMap<>();
|
|
|
|
// filter out all fields we don't need
|
|
for (int i = 0; i < this.accessors.size(); i++) {
|
|
FieldAccessor accessor = this.accessors.get(i);
|
|
Field field = accessor.getField();
|
|
|
|
// check if the field type matches
|
|
if (!fieldType.isAssignableFrom(field.getType())) {
|
|
continue;
|
|
}
|
|
|
|
// check if we need to check for parameters
|
|
if (paramTypes.length > 0) {
|
|
// check if the field is parameterized
|
|
Type generic = field.getGenericType();
|
|
if (!(generic instanceof ParameterizedType)) {
|
|
continue;
|
|
}
|
|
|
|
// check if the type arguments of the field are matching
|
|
ParameterizedType parameterized = (ParameterizedType) generic;
|
|
if (!Arrays.equals(parameterized.getActualTypeArguments(), paramTypes)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// this field should be included
|
|
fields.add(accessor);
|
|
if (this.defaultFields.containsKey(accessor)) {
|
|
defaults.put(accessor, i);
|
|
}
|
|
}
|
|
|
|
// Cache structure modifiers
|
|
result = this.withFieldType(fieldType, fields, defaults);
|
|
this.subtypeCache.put(fieldType, result);
|
|
}
|
|
|
|
// Add the target too
|
|
result = result.withTarget(this.target);
|
|
result.converter = converter;
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create a new structure modifier for the new field type.
|
|
*
|
|
* @param <V> Type
|
|
* @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.
|
|
* @return A new structure modifier.
|
|
*/
|
|
protected <V> StructureModifier<V> withFieldType(
|
|
Class<?> fieldType,
|
|
List<FieldAccessor> filtered,
|
|
Map<FieldAccessor, Integer> defaults
|
|
) {
|
|
return this.withFieldType(fieldType, filtered, defaults, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new structure modifier for the new field type.
|
|
*
|
|
* @param <V> Type
|
|
* @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.
|
|
* @return A new structure modifier.
|
|
*/
|
|
@SuppressWarnings("SameParameterValue") // api method, maybe someone needs it
|
|
protected <V> StructureModifier<V> withFieldType(
|
|
Class<?> fieldType,
|
|
List<FieldAccessor> filtered,
|
|
Map<FieldAccessor, Integer> defaults,
|
|
EquivalentConverter<V> converter
|
|
) {
|
|
StructureModifier<V> result = new StructureModifier<>();
|
|
result.initialize(
|
|
this.targetType,
|
|
fieldType,
|
|
filtered,
|
|
defaults,
|
|
converter,
|
|
new HashMap<>());
|
|
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<T> withTarget(Object target) {
|
|
StructureModifier<T> copy = new StructureModifier<>();
|
|
|
|
// Create a new instance
|
|
copy.initialize(this);
|
|
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 <V> StructureModifier<V> withConverter(EquivalentConverter<V> converter) {
|
|
StructureModifier<V> copy = (StructureModifier<V>) this.withTarget(this.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<T> converter) {
|
|
this.converter = converter;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "StructureModifier[fieldType=" + this.fieldType + ", data=" + this.accessors + "]";
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Iterator<StructureModifierIntermediate<T>> iterator() {
|
|
return new StructureModifierIterator<T>(this);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Spliterator<StructureModifierIntermediate<T>> spliterator() {
|
|
return Spliterators.spliterator(this.iterator(), this.size(), Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SIZED);
|
|
}
|
|
}
|