/** * 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; 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: * * * @author dmulloy2 */ public class AutoWrapper implements EquivalentConverter { private static final Object[] NO_ARGS = new Object[0]; private Map> wrappers = new HashMap<>(); private Map> unwrappers = new HashMap<>(); // lazy private FieldAccessor[] nmsAccessors; private FieldAccessor[] wrapperAccessors; private Object[] nmsDefaultArgs; private ConstructorAccessor nmsInstanceCreator; private Class wrapperClass; private Class nmsClass; private AutoWrapper(Class wrapperClass, Class nmsClass) { this.wrapperClass = wrapperClass; this.nmsClass = nmsClass; } public static AutoWrapper wrap(Class wrapperClass, Class nmsClass) { return new AutoWrapper<>(wrapperClass, nmsClass); } public static AutoWrapper wrap(Class wrapperClass, String nmsClassName) { return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName)); } public static AutoWrapper wrap(Class wrapperClass, String nmsClassName, String... aliases) { return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases)); } public AutoWrapper field(int index, Function wrapper, Function unwrapper) { wrappers.put(index, wrapper); unwrappers.put(index, unwrapper); return this; } public AutoWrapper field(int index, EquivalentConverter converter) { return field(index, converter::getSpecific, specific -> converter.getGeneric(specific)); } public T wrap(Object 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) { // 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(field -> Accessors.getFieldAccessor(field)) .toArray(FieldAccessor[]::new); } if (wrapperAccessors == null) { wrapperAccessors = Arrays .stream(wrapperClass.getDeclaredFields()) .map(field -> Accessors.getFieldAccessor(field)) .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 getSpecificType() { return wrapperClass; } public static class InvalidWrapperException extends RuntimeException { private InvalidWrapperException(String message, Throwable cause) { super(message, cause); } } }