2022-12-07 19:52:09 +01:00
|
|
|
/**
|
|
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
|
|
* Copyright (C) 2017 Dan Mulloy
|
|
|
|
*
|
|
|
|
* 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.wrappers;
|
|
|
|
|
|
|
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
|
|
|
import com.google.common.base.Defaults;
|
2023-03-24 02:23:10 +01:00
|
|
|
import com.google.common.base.Preconditions;
|
2022-12-07 19:52:09 +01:00
|
|
|
|
|
|
|
import java.lang.reflect.Modifier;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
|
|
import com.comphenix.protocol.reflect.EquivalentConverter;
|
|
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Automatically wraps an internal NMS class to a non-versioned, deofbuscated class.
|
|
|
|
* Requirements:
|
|
|
|
* <ul>
|
|
|
|
* <li>The wrapper must be public</li>
|
|
|
|
* <li>If the wrapper is an internal class, it must be static</li>
|
|
|
|
* <li>The wrapper must have one public constructor with no arguments (the default constructor is acceptable)</li>
|
|
|
|
* <li>The wrapper must have the the same number of fields as the NMS class</li>
|
|
|
|
* <li>Each field should correspond, in order, to its NMS counterpart</li>
|
|
|
|
* <li>Non-generic fields must have a converter</li>
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* @author dmulloy2
|
|
|
|
*/
|
|
|
|
public class AutoWrapper<T> implements EquivalentConverter<T> {
|
2023-05-12 16:35:34 +02:00
|
|
|
private static final Object[] NO_ARGS = new Object[0];
|
2022-12-07 19:52:09 +01:00
|
|
|
|
2023-05-12 16:35:34 +02:00
|
|
|
private Map<Integer, Function<Object, Object>> wrappers = new HashMap<>();
|
|
|
|
private Map<Integer, Function<Object, Object>> unwrappers = new HashMap<>();
|
2022-12-07 19:52:09 +01:00
|
|
|
|
2023-05-12 16:35:34 +02:00
|
|
|
// lazy
|
|
|
|
private FieldAccessor[] nmsAccessors;
|
|
|
|
private FieldAccessor[] wrapperAccessors;
|
2022-12-07 19:52:09 +01:00
|
|
|
|
2023-05-12 16:35:34 +02:00
|
|
|
private Object[] nmsDefaultArgs;
|
|
|
|
private ConstructorAccessor nmsInstanceCreator;
|
2022-12-07 19:52:09 +01:00
|
|
|
|
2023-05-12 16:35:34 +02:00
|
|
|
private Class<T> wrapperClass;
|
|
|
|
private Class<?> nmsClass;
|
2022-12-07 19:52:09 +01:00
|
|
|
|
2023-05-12 16:35:34 +02:00
|
|
|
private AutoWrapper(Class<T> wrapperClass, Class<?> nmsClass) {
|
|
|
|
this.wrapperClass = wrapperClass;
|
|
|
|
this.nmsClass = nmsClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, Class<?> nmsClass) {
|
|
|
|
return new AutoWrapper<>(wrapperClass, nmsClass);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName) {
|
|
|
|
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName, String... aliases) {
|
|
|
|
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases));
|
|
|
|
}
|
|
|
|
|
|
|
|
public AutoWrapper<T> field(int index, Function<Object, Object> wrapper, Function<Object, Object> unwrapper) {
|
|
|
|
wrappers.put(index, wrapper);
|
|
|
|
unwrappers.put(index, unwrapper);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public AutoWrapper<T> field(int index, EquivalentConverter converter) {
|
|
|
|
return field(index, converter::getSpecific, specific -> converter.getGeneric(specific));
|
|
|
|
}
|
|
|
|
|
|
|
|
public T wrap(Object nmsObject) {
|
|
|
|
Preconditions.checkNotNull(nmsObject);
|
|
|
|
|
|
|
|
T instance;
|
|
|
|
|
|
|
|
try {
|
|
|
|
instance = wrapperClass.newInstance();
|
|
|
|
} catch (ReflectiveOperationException ex) {
|
|
|
|
throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensures that all accessors are present
|
|
|
|
computeFieldAccessors();
|
|
|
|
|
|
|
|
for (int i = 0; i < wrapperAccessors.length; i++) {
|
|
|
|
FieldAccessor source = nmsAccessors[i];
|
|
|
|
FieldAccessor target = wrapperAccessors[i];
|
|
|
|
|
|
|
|
Object value = source.get(nmsObject);
|
|
|
|
if (wrappers.containsKey(i))
|
|
|
|
value = wrappers.get(i).apply(value);
|
|
|
|
|
|
|
|
target.set(instance, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Object unwrap(Object wrapper) {
|
|
|
|
Preconditions.checkNotNull(wrapper);
|
|
|
|
|
|
|
|
// ensures that all accessors are present
|
|
|
|
computeFieldAccessors();
|
|
|
|
computeNmsConstructorAccess();
|
|
|
|
|
|
|
|
Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs);
|
|
|
|
|
|
|
|
for (int i = 0; i < wrapperAccessors.length; i++) {
|
|
|
|
FieldAccessor source = wrapperAccessors[i];
|
|
|
|
FieldAccessor target = nmsAccessors[i];
|
|
|
|
|
|
|
|
Object value = source.get(wrapper);
|
|
|
|
if (unwrappers.containsKey(i))
|
|
|
|
value = unwrappers.get(i).apply(value);
|
|
|
|
|
|
|
|
target.set(instance, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void computeFieldAccessors() {
|
|
|
|
if (nmsAccessors == null) {
|
|
|
|
nmsAccessors = Arrays
|
|
|
|
.stream(nmsClass.getDeclaredFields())
|
|
|
|
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
|
|
|
.map(Accessors::getFieldAccessor)
|
|
|
|
.toArray(FieldAccessor[]::new);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wrapperAccessors == null) {
|
|
|
|
wrapperAccessors = Arrays
|
|
|
|
.stream(wrapperClass.getDeclaredFields())
|
2023-05-15 15:16:36 +02:00
|
|
|
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
2023-05-12 16:35:34 +02:00
|
|
|
.map(Accessors::getFieldAccessor)
|
|
|
|
.toArray(FieldAccessor[]::new);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void computeNmsConstructorAccess() {
|
|
|
|
if (nmsInstanceCreator == null) {
|
|
|
|
ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass);
|
|
|
|
if (noArgs != null) {
|
|
|
|
// no args constructor is available - use it
|
|
|
|
nmsInstanceCreator = noArgs;
|
|
|
|
nmsDefaultArgs = NO_ARGS;
|
|
|
|
} else {
|
|
|
|
// use the first constructor of the class
|
|
|
|
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
|
|
|
|
nmsDefaultArgs = Arrays
|
|
|
|
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
|
|
|
|
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
|
|
|
|
.toArray(Object[]::new);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---- Equivalent conversion
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public T getSpecific(Object generic) {
|
|
|
|
return wrap(generic);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Object getGeneric(Object specific) {
|
|
|
|
return unwrap(specific);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Class<T> getSpecificType() {
|
|
|
|
return wrapperClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class InvalidWrapperException extends RuntimeException {
|
|
|
|
private InvalidWrapperException(String message, Throwable cause) {
|
|
|
|
super(message, cause);
|
|
|
|
}
|
|
|
|
}
|
2022-12-07 19:52:09 +01:00
|
|
|
}
|