descriptors = descriptorMap.computeIfAbsent(name, k -> new HashMap<>(3));
String descriptor = descritporProcessor.apply(method);
if (descriptors.put(descriptor, method) != null) {
- throw new IllegalArgumentException("Method named '" + name + "' with descriptor '" + descriptors + "' was already added: " + descriptorMap);
+ throw new IllegalArgumentException("Method named '" + name + "' with descriptor '" + descriptor + "' was already added: " + descriptors);
}
}
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxy.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxy.java
index 2fde5c9..174cfe9 100644
--- a/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxy.java
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxy.java
@@ -27,6 +27,7 @@
import com.cryptomorin.xseries.reflection.jvm.objects.ReflectedObject;
import com.cryptomorin.xseries.reflection.proxy.annotations.Constructor;
import com.cryptomorin.xseries.reflection.proxy.annotations.Field;
+import com.cryptomorin.xseries.reflection.proxy.annotations.Proxify;
import com.cryptomorin.xseries.reflection.proxy.processors.MappedType;
import com.cryptomorin.xseries.reflection.proxy.processors.ProxyMethodInfo;
import com.cryptomorin.xseries.reflection.proxy.processors.ReflectiveAnnotationProcessor;
@@ -35,6 +36,7 @@
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@@ -43,7 +45,7 @@
/**
* The basis of this class is that you create an {@code interface} class and annotate it using
- * {@link com.cryptomorin.xseries.reflection.proxy.annotations.Class Class}, {@link Field}, {@link Constructor}, etc... annotations to mark how
+ * {@link Proxify Class}, {@link Field}, {@link Constructor}, etc... annotations to mark how
* these methods are resolved for different versions. Everything is accessed through methods,
* even constructors and fields are accessed in forms of methods.
*
@@ -94,8 +96,12 @@ public static ReflectiveProxy proxify(Class
for (ProxyMethodInfo overload : mapping.getValue().getOverloads()) {
ReflectedObject jvm = overload.handle.jvm().unreflect();
+ MethodHandle methodHandle = (MethodHandle) overload.handle.unreflect();
+
+ methodHandle = createDynamicProxy(null, methodHandle);
+
ProxifiedObject proxifiedObj = new ProxifiedObject(
- (MethodHandle) overload.handle.unreflect(),
+ methodHandle,
overload,
jvm.accessFlags().contains(XAccessFlag.STATIC),
jvm.type() == ReflectedObject.Type.CONSTRUCTOR,
@@ -136,6 +142,22 @@ public static ReflectiveProxy proxify(Class
return proxy;
}
+ private static MethodHandle createDynamicProxy(@Nullable Object bindInstance, MethodHandle methodHandle) {
+ int parameterCount = methodHandle.type().parameterCount();
+ int requireArgs = bindInstance != null ? 1 : 0;
+
+ // bind the only parameter left and remove it.
+ if (bindInstance != null) methodHandle = methodHandle.bindTo(bindInstance);
+
+ if (parameterCount == requireArgs) {
+ return methodHandle.asType(MethodType.methodType(Object.class));
+ } else {
+ return methodHandle
+ .asSpreader(Object[].class, parameterCount - requireArgs)
+ .asType(MethodType.methodType(Object.class, Object[].class));
+ }
+ }
+
private static String descriptorProcessor(ProxifiedObject obj) {
// We can't use the MethodHandle here because the parameter list might contain the descriptor for the receiver object.
return OverloadedMethod.getParameterDescriptor(MappedType.getRealTypes(obj.proxyMethodInfo.pTypes));
@@ -243,7 +265,15 @@ public T bindTo(@NotNull Object instance) {
} else {
try {
// insert = MethodHandles.insertArguments(unbound.handle, 0, instance);
- insert = unbound.handle.bindTo(instance);
+ // This is cached, no worries.
+ insert = (MethodHandle) unbound.proxyMethodInfo.handle.unreflect();
+
+ // We already checked for static and constructor members, all these handles are for instance members now.
+ if (insert.type().parameterCount() == 0) {
+ throw new IllegalStateException("Non-static, non-constructor with 0 arguments found: " + insert);
+ } else {
+ insert = createDynamicProxy(instance, insert);
+ }
} catch (Exception e) {
throw new IllegalStateException("Failed to bind " + instance + " to " + entry.getKey() + " -> " + unbound.handle + " (static=" + unbound.isStatic + ", constructor=" + unbound.isConstructor + ')', e);
}
@@ -281,7 +311,7 @@ private static String getMethodList(Class> clazz, boolean declaredOnly) {
}
@Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
{ // ReflectiveProxyObject & Object methods
int paramCount = method.getParameterCount();
String name = method.getName();
@@ -306,14 +336,15 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
if (instance == null) proxyClass.wait();
else instance.wait();
return null;
- case "getClass": // Does this actually get called?
- if (instance == null) return proxyClass;
- else return instance.getClass();
+ case "getTargetClass":
+ return targetClass;
}
} else if (paramCount == 1) {
switch (name) {
case "bindTo":
return bindTo(args[0]);
+ case "isInstance":
+ return targetClass.isInstance(args[0]);
case "equals":
return instance == null ? proxyClass == args[0] : instance.equals(args[0]);
case "wait":
@@ -354,7 +385,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
Object result;
- if (reflectedHandle.pTypes != null) {
+ if (reflectedHandle.pTypes != null && args != null) {
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof ReflectiveProxyObject) {
@@ -367,8 +398,14 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
// MethodHandle#invoke is a special case due to its @PolymorphicSignature nature.
// The signature of the method is simply a placeholder which is replaced by JVM.
// We use invokeWithArguments which accepts working with Object.class
- if (args == null) result = reflectedHandle.handle.invoke();
- else result = reflectedHandle.handle.invokeWithArguments(args);
+ // But we already changed the method signature to (Object[])Object so we can safely
+ // use invokeExact()
+ if (args == null) {
+ result = reflectedHandle.handle.invokeExact();
+ } else {
+ // result = reflectedHandle.handle.invokeWithArguments(args);
+ result = reflectedHandle.handle.invoke(args);
+ }
} catch (Throwable ex) {
throw new IllegalStateException("Failed to execute " + method + " -> "
+ reflectedHandle.handle + " with args "
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxyObject.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxyObject.java
index bd7eb8d..31a86ae 100644
--- a/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxyObject.java
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/ReflectiveProxyObject.java
@@ -24,6 +24,7 @@
import com.cryptomorin.xseries.reflection.proxy.annotations.Ignore;
import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,17 +42,50 @@
*/
@ApiStatus.Experimental
public interface ReflectiveProxyObject {
- @Nullable
+ /**
+ * Gets the instance that the methods are delegating to.
+ * Throw an exception if used on the factory object.
+ */
@Ignore
+ @NotNull
+ @ApiStatus.NonExtendable
+ @Contract(pure = true)
Object instance();
+ /**
+ * Get the real class object that this proxy object is referencing.
+ *
+ * @since 14.0.0
+ */
+ @Ignore
+ @NotNull
+ @ApiStatus.NonExtendable
+ @Contract(pure = true)
+ Class> getTargetClass();
+
+ /**
+ * Equivalent to the code:
+ * {@code object instanceof TargetClass}
+ * This results in more performance in generated code instead of doing:
+ * {@code getTargetClass().isInstance(object.getClass())}
+ * So do note that this will never return true if you pass a {@link ReflectiveProxyObject} to it.
+ *
+ * @since 14.0.0
+ */
+ @Ignore
+ @NotNull
+ @ApiStatus.NonExtendable
+ @Contract(pure = true)
+ boolean isInstance(@Nullable Object object);
+
/**
* Returns a new {@link ReflectiveProxyObject} that's linked to a new {@link ReflectiveProxy} with the given instance.
*
* @param instance the instance to bind.
*/
+ @Ignore
@NotNull
@ApiStatus.OverrideOnly
- @Ignore
+ @Contract(value = "_ -> new", pure = true)
ReflectiveProxyObject bindTo(@NotNull Object instance);
}
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Class.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Proxify.java
similarity index 98%
rename from src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Class.java
rename to src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Proxify.java
index ac9d69b..eb337fb 100644
--- a/src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Class.java
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/annotations/Proxify.java
@@ -29,7 +29,7 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
-public @interface Class {
+public @interface Proxify {
java.lang.Class> target() default void.class;
String packageName() default "";
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/generator/XProxifier.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/generator/XProxifier.java
new file mode 100644
index 0000000..920a186
--- /dev/null
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/generator/XProxifier.java
@@ -0,0 +1,494 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Crypto Morin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.cryptomorin.xseries.reflection.proxy.generator;
+
+import com.cryptomorin.xseries.reflection.XAccessFlag;
+import com.cryptomorin.xseries.reflection.jvm.objects.ReflectedObject;
+import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxyObject;
+import com.cryptomorin.xseries.reflection.proxy.annotations.*;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * This class is used for scanning other compiled classes using reflection in order to generate template
+ * interfaces classes for {@link com.cryptomorin.xseries.reflection.XReflection#proxify(Class)}.
+ *
+ * Turn this into an IntelliJ plugin?
+ * TODO Add a way to determine which referenced classes also require proxy classes
+ * and rename those, and add a method for generating a string for them as well.
+ *
+ * @since 14.0.0
+ */
+@SuppressWarnings("StringBufferField")
+@ApiStatus.Internal
+public final class XProxifier {
+ private static final String MEMBER_SPACES = " ";
+
+ private final StringBuilder writer = new StringBuilder(1000);
+ private final Set imports = new HashSet<>(20);
+
+ private final String proxifiedClassName;
+ private final Class> clazz;
+
+ //// Settings //// TODO implement this
+ private final boolean generateIntelliJAnnotations = true;
+ private final boolean generateInaccessibleMembers = true;
+ private final boolean copyAnnotations = true;
+ private final boolean writeComments = true;
+ private final boolean writeInfoAnnotationsAsComments = true; // like doing /* private static final */ before the member
+ private boolean disableIDEFormatting;
+ private Function, String> remapper; // useful for mapping classes to a hypothetical proxy class
+
+ public XProxifier(Class> clazz) {
+ this.clazz = clazz;
+ this.proxifiedClassName = clazz.getSimpleName() + "Proxified";
+ proxify();
+ }
+
+ private static Class> unwrapArrayType(Class> clazz) {
+ while (clazz.isArray()) clazz = clazz.getComponentType();
+ return clazz;
+ }
+
+ private void imports(Class> clazz) {
+ clazz = unwrapArrayType(clazz);
+ if (!clazz.isPrimitive() && !clazz.getPackage().getName().equals("java.lang"))
+ imports.add(clazz.getName().replace('$', '.'));
+ }
+
+ private void writeComments(String... comments) {
+ boolean multiLine = comments.length > 1;
+ if (!multiLine) {
+ writer.append("// ").append(comments[0]).append('\n');
+ }
+
+ writer.append("/**\n");
+
+ for (String comment : comments) {
+ writer.append(" * ");
+ writer.append(comment);
+ writer.append('\n');
+ }
+
+ writer.append(" */\n");
+ }
+
+ private void writeThrownExceptions(Class>[] exceptionTypes) {
+ if (exceptionTypes == null || exceptionTypes.length == 0) return;
+ writer.append(" throws ");
+ StringJoiner exs = new StringJoiner(", ");
+ for (Class> ex : exceptionTypes) {
+ imports(ex);
+ exs.add(ex.getSimpleName());
+ }
+ writer.append(exs);
+ }
+
+ private void writeMember(ReflectedObject jvm) {
+ writeMember(jvm, false);
+ }
+
+ private void writeMember(ReflectedObject jvm, boolean generateGetterField) {
+ writer.append(annotationsToString(true, true, jvm));
+
+ Set accessFlags = jvm.accessFlags();
+ if (accessFlags.contains(XAccessFlag.PRIVATE)) writeAnnotation(Private.class);
+ if (accessFlags.contains(XAccessFlag.PROTECTED)) writeAnnotation(Protected.class);
+ if (accessFlags.contains(XAccessFlag.STATIC)) writeAnnotation(Static.class);
+ if (accessFlags.contains(XAccessFlag.FINAL)) writeAnnotation(Final.class);
+
+ switch (jvm.type()) {
+ case CONSTRUCTOR:
+ writeAnnotation(com.cryptomorin.xseries.reflection.proxy.annotations.Constructor.class);
+ writeAnnotation("NotNull");
+
+ Constructor> ctor = (Constructor>) jvm.unreflect();
+ String contractParams = Arrays.stream(ctor.getParameterTypes()).map(x -> "_").collect(Collectors.joining(", "));
+ writeAnnotation("Contract",
+ "value = \"" + contractParams + " -> new\"",
+ "pure = true"
+ );
+
+ break;
+ case FIELD:
+ writeAnnotation(com.cryptomorin.xseries.reflection.proxy.annotations.Field.class);
+ if (generateGetterField) {
+ writeAnnotation("Contract", "pure = true");
+ } else {
+ writeAnnotation("Contract", "mutates = \"this\"");
+ }
+ break;
+ }
+
+ StringJoiner parameters = new StringJoiner(", ", "(", ")");
+ Class>[] exceptionTypes = null;
+ writer.append(MEMBER_SPACES);
+ switch (jvm.type()) {
+ case CONSTRUCTOR:
+ Constructor> constructor = (Constructor>) jvm.unreflect();
+ exceptionTypes = constructor.getExceptionTypes();
+ writer.append(proxifiedClassName).append(' ').append("construct");
+ writeParameters(parameters, constructor.getParameters());
+ break;
+ case FIELD:
+ Field field = (Field) jvm.unreflect();
+ imports(field.getType());
+
+ if (generateGetterField) {
+ writer.append(field.getType().getSimpleName());
+ } else {
+ writer.append("void");
+ parameters.add(field.getType().getSimpleName() + " value");
+ }
+
+ writer.append(' ');
+ writer.append(jvm.name());
+ break;
+ case METHOD:
+ Method method = (Method) jvm.unreflect();
+ exceptionTypes = method.getExceptionTypes();
+
+ imports(method.getReturnType());
+
+ writer.append(method.getReturnType().getSimpleName());
+ writer.append(' ');
+ writer.append(jvm.name());
+ writeParameters(parameters, method.getParameters());
+ break;
+ }
+
+ writer.append(parameters);
+ writeThrownExceptions(exceptionTypes);
+ writer.append(";\n\n");
+ }
+
+ /**
+ * Boxes primitive types into an object because a primitive array like int[] cannot be cast to Object[]
+ */
+ private static Object[] getArray(Object val) {
+ if (val instanceof Object[]) return (Object[]) val;
+ int arrlength = Array.getLength(val);
+ Object[] outputArray = new Object[arrlength];
+ for (int i = 0; i < arrlength; ++i) {
+ outputArray[i] = Array.get(val, i);
+ }
+ return outputArray;
+ }
+
+ private String constantToString(Object obj) {
+ if (obj instanceof String) return '"' + obj.toString() + '"';
+ if (obj instanceof Class) {
+ Class> clazz = (Class>) obj;
+ imports(clazz);
+ return clazz.getSimpleName() + ".class";
+ }
+ if (obj instanceof Annotation) {
+ Annotation annotation = (Annotation) obj;
+ return annotationToString(annotation);
+ }
+ if (obj.getClass().isEnum()) {
+ imports(obj.getClass());
+ return obj.getClass().getSimpleName() + '.' + ((Enum>) obj).name();
+ }
+ if (obj.getClass().isArray()) {
+ // Multidimensional arrays aren't allowed in annotations.
+ Object[] array = getArray(obj);
+ StringJoiner builder;
+ if (array.length == 0) return "{}";
+ if (array.length == 1) builder = new StringJoiner(", ");
+ else builder = new StringJoiner(", ", "{", "}");
+
+ for (Object element : array) {
+ builder.add(constantToString(element));
+ }
+
+ return builder.toString();
+ }
+
+ // Numbers and booleans
+ return obj.toString();
+ }
+
+ private String annotationsToString(boolean member, boolean newLine, AnnotatedElement annotatable) {
+ StringJoiner builder = new StringJoiner(
+ (newLine ? '\n' : "") + (member ? MEMBER_SPACES : ""),
+ (member ? MEMBER_SPACES : ""),
+ (newLine ? "\n" : "")
+ ).setEmptyValue("");
+
+ for (Annotation annotation : annotatable.getAnnotations()) {
+ Annotation[] unwrapped = unwrapRepeatElement(annotation);
+ if (unwrapped != null) {
+ for (Annotation inner : unwrapped) {
+ builder.add(annotationToString(inner));
+ }
+ } else {
+ builder.add(annotationToString(annotation));
+ }
+ }
+
+ return builder.toString();
+ }
+
+ private static Annotation[] unwrapRepeatElement(Annotation annotation) {
+ try {
+ Method method = annotation.annotationType().getDeclaredMethod("value");
+ if (method.getReturnType().isArray()) {
+ // Multidimensional arrays aren't allowed, but we will use this just in case.
+ Class> rawReturn = unwrapArrayType(method.getReturnType());
+ if (rawReturn.isAnnotation()) {
+ Repeatable repeatable = rawReturn.getAnnotation(Repeatable.class);
+ if (repeatable != null && repeatable.value() == annotation.annotationType()) {
+ try {
+ return (Annotation[]) method.invoke(annotation);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
+ }
+ } catch (NoSuchMethodException ignored) {
+ }
+ return null;
+ }
+
+ private String annotationToString(Annotation annotation) {
+ List builder = new ArrayList<>();
+ boolean visitedValue = false;
+
+ for (Method entry : annotation.annotationType().getDeclaredMethods()) {
+ try {
+ entry.setAccessible(true);
+ String key = entry.getName();
+ Object value = entry.invoke(annotation);
+ try {
+ @Nullable Object defaultValue = entry.getDefaultValue();
+
+ // The default value isn't directly passed, they're not actually identical.
+ if (defaultValue != null) {
+ if (defaultValue.getClass().isArray()) {
+ if (Arrays.equals(getArray(defaultValue), getArray(value))) continue;
+ } else {
+ if (value.equals(defaultValue)) continue;
+ }
+ }
+ } catch (TypeNotPresentException ignored) {
+ // If it's not an annotation.
+ }
+
+ if (key.equals("value")) visitedValue = true;
+ builder.add(key + " = " + constantToString(value));
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException("Failed to get annotation value " + entry, e);
+ }
+ }
+
+ imports(annotation.annotationType());
+ String annotationValues;
+ if (builder.isEmpty()) annotationValues = "";
+ else if (builder.size() == 1 && visitedValue) {
+ annotationValues = builder.get(0);
+ int equalsSign = annotationValues.indexOf('=');
+ annotationValues = '(' + annotationValues.substring(equalsSign + 2) + ')';
+ } else {
+ annotationValues = '(' + String.join(", ", builder) + ')';
+ }
+
+ return '@' + annotation.annotationType().getSimpleName() + annotationValues;
+ }
+
+ private StringJoiner writeParameters(StringJoiner joiner, Parameter[] parameters) {
+ for (Parameter parameter : parameters) {
+ imports(parameter.getType());
+
+ String type;
+ if (parameter.isVarArgs()) {
+ type = parameter.getType().getSimpleName() + "... ";
+ } else {
+ type = parameter.getType().getSimpleName();
+ }
+
+ String annotations = annotationsToString(false, false, parameter);
+ joiner.add(annotations + (annotations.isEmpty() ? "" : " ") + type + ' ' + parameter.getName());
+ }
+ return joiner;
+ }
+
+ private void writeAnnotation(Class> annotation, String... values) {
+ writeAnnotation(true, annotation, values);
+ }
+
+ private void writeAnnotation(boolean member, Class> annotation, String... values) {
+ imports(annotation);
+ writeAnnotation(member, annotation.getSimpleName(), values);
+ }
+
+ private void writeAnnotation(String annotation, String... values) {
+ writeAnnotation(true, annotation, values);
+ }
+
+ private void writeAnnotation(boolean member, String annotation, String... values) {
+ if (member) writer.append(MEMBER_SPACES);
+ writer.append('@').append(annotation);
+ if (values.length != 0) {
+ StringJoiner valueJoiner = new StringJoiner(", ", "(", ")");
+ for (String value : values) valueJoiner.add(value);
+ writer.append(valueJoiner);
+ }
+ writer.append('\n');
+ }
+
+ private void proxify() {
+ if (disableIDEFormatting) {
+ // This is intentionally written like this because IntelliJ will even recognize this
+ // text sequence even if it's not written as a comment.
+ writer.append("// ").append("@formatter:").append("OFF").append('\n');
+ }
+
+ if (writeComments) {
+ writeComments(
+ "This is a generated proxified class for " + clazz.getSimpleName() + ". However, you might",
+ "want to review each member and correct its annotations when needed.",
+ "",
+ "It's also recommended to use your IDE's code formatter to adjust",
+ "imports and spaces according to your settings.",
+ "In IntelliJ, this can be done by with Ctrl+Alt+L",
+ "
",
+ "Full Target Class Path:",
+ clazz.getName()
+ );
+ }
+
+ writer.append(annotationsToString(false, true, clazz));
+
+ writeAnnotation(
+ false,
+ Proxify.class,
+ "target = " + clazz.getSimpleName() + ".class"
+ );
+ if (!XAccessFlag.PUBLIC.isSet(clazz.getModifiers())) {
+ writeAnnotation(false, Private.class);
+ }
+ if (XAccessFlag.FINAL.isSet(clazz.getModifiers())) {
+ writeAnnotation(false, Final.class);
+ writeAnnotation(false, "ApiStatus.NonExtendable");
+ }
+ writer
+ .append("public interface ")
+ .append(proxifiedClassName)
+ .append(" extends ")
+ .append(ReflectiveProxyObject.class.getSimpleName())
+ .append(" {\n");
+
+ Field[] declaredFields = clazz.getDeclaredFields();
+ for (Field field : declaredFields) {
+ if (field.isSynthetic()) continue;
+ if (!XAccessFlag.FINAL.isSet(field.getModifiers())) {
+ writeMember(ReflectedObject.of(field), false);
+ }
+ writeMember(ReflectedObject.of(field), true);
+ }
+ if (declaredFields.length != 0) writer.append('\n');
+
+ Constructor>[] declaredConstructors = clazz.getDeclaredConstructors();
+ for (Constructor> constructor : declaredConstructors) {
+ if (constructor.isSynthetic()) continue;
+ writeMember(ReflectedObject.of(constructor));
+ }
+ if (declaredConstructors.length != 0) writer.append('\n');
+
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (method.getDeclaringClass() == Object.class) continue;
+ if (method.isSynthetic()) continue;
+ if (method.isBridge()) continue;
+ writeMember(ReflectedObject.of(method));
+ }
+
+ writer.append('\n');
+ writeAnnotation(Ignore.class);
+ writeAnnotation("NotNull");
+ writeAnnotation("ApiStatus.OverrideOnly");
+ writeAnnotation("Contract",
+ "value = \"_ -> new\"",
+ "pure = true"
+ );
+ writer.append(MEMBER_SPACES).append(proxifiedClassName).append(" bindTo(@NotNull Object instance);\n");
+
+ writer.append("}\n");
+ finalizeString();
+ }
+
+ /**
+ * After gathering all analysis data (currently only imports), construct the final string.
+ */
+ private void finalizeString() {
+ StringBuilder whole = new StringBuilder(writer.length() + (imports.size() * 100));
+ whole.append("import org.jetbrains.annotations.*;\n");
+ // whole.append("import ").append(com.cryptomorin.xseries.reflection.proxy.annotations.Field.class.getPackage().getName()).append(".*;\n");
+
+ List sortedImports = new ArrayList<>(imports);
+ sortedImports.sort(Comparator.naturalOrder());
+
+ for (String anImport : sortedImports) {
+ whole.append("import ").append(anImport).append(";\n");
+ }
+
+ whole.append('\n');
+ this.writer.insert(0, whole);
+ imports(ReflectiveProxyObject.class);
+ }
+
+
+ public String getString() {
+ if (writer.length() == 0) proxify();
+ return writer.toString();
+ }
+
+ public void writeTo(Path path) {
+ if (Files.isDirectory(path)) {
+ path = path.resolve(proxifiedClassName + ".java");
+ }
+
+ try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
+ StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ writer.write(getString());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ProxyMethodInfo.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ProxyMethodInfo.java
index 3679a4f..32aee1e 100644
--- a/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ProxyMethodInfo.java
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ProxyMethodInfo.java
@@ -23,9 +23,11 @@
package com.cryptomorin.xseries.reflection.proxy.processors;
import com.cryptomorin.xseries.reflection.ReflectiveHandle;
+import org.jetbrains.annotations.ApiStatus;
import java.lang.reflect.Method;
+@ApiStatus.Internal
public class ProxyMethodInfo {
public final ReflectiveHandle> handle;
public final Method interfaceMethod;
@@ -38,4 +40,9 @@ public ProxyMethodInfo(ReflectiveHandle> handle, Method interfaceMethod, Mappe
this.rType = rType;
this.pTypes = pTypes;
}
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + '(' + interfaceMethod + ')';
+ }
}
diff --git a/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ReflectiveAnnotationProcessor.java b/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ReflectiveAnnotationProcessor.java
index f9c9425..1891228 100644
--- a/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ReflectiveAnnotationProcessor.java
+++ b/src/main/java/com/cryptomorin/xseries/reflection/proxy/processors/ReflectiveAnnotationProcessor.java
@@ -40,7 +40,6 @@
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
-import java.lang.Class;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.AnnotatedElement;
@@ -52,7 +51,6 @@
public final class ReflectiveAnnotationProcessor {
private final Class extends ReflectiveProxyObject> interfaceClass;
private ClassOverloadedMethods mapped;
- private Function descriptorProcessor;
private Class> targetClass;
@@ -104,7 +102,6 @@ private void loadDependency(MappedType type, Function, Boolean> isLoade
}
public void process(Function descriptorProcessor) {
- this.descriptorProcessor = descriptorProcessor;
ClassHandle classHandle = processTargetClass();
Method[] interfaceMethods = interfaceClass.getMethods(); // It's an interface, all are public
OverloadedMethod.Builder mappedHandles = new OverloadedMethod.Builder<>(descriptorProcessor);
@@ -139,10 +136,10 @@ public void process(Function descriptorProcessor) {
error("Field setter method must have only one parameter: " + method);
}
- Class> parameterType = method.getParameterTypes()[0];
- rType = unwrap(parameterType);
-
- field.returns(rType.real);
+ MappedType fieldType = unwrap(method.getParameterTypes()[0]);
+ rType = new MappedType(void.class, void.class);
+ pTypes = new MappedType[]{fieldType};
+ field.returns(fieldType.real);
} else {
field.getter();
if (method.getParameterCount() != 0) {
@@ -191,6 +188,7 @@ public void process(Function descriptorProcessor) {
error("Failed to map " + method, e);
}
+ System.out.println("Adding method of type " + method.getName() + ": " + rType + " - " + Arrays.toString(pTypes));
ProxyMethodInfo methodInfo = new ProxyMethodInfo(cached, method, rType, pTypes);
mappedHandles.add(methodInfo, method.getName());
}
@@ -199,8 +197,8 @@ public void process(Function descriptorProcessor) {
}
public @NotNull ClassHandle processTargetClass() {
- com.cryptomorin.xseries.reflection.proxy.annotations.Class reflectClass =
- interfaceClass.getAnnotation(com.cryptomorin.xseries.reflection.proxy.annotations.Class.class);
+ Proxify reflectClass =
+ interfaceClass.getAnnotation(Proxify.class);
ReflectMinecraftPackage mcClass = interfaceClass.getAnnotation(ReflectMinecraftPackage.class);
if (reflectClass == null && mcClass == null) {
diff --git a/src/test/com/cryptomorin/xseries/test/Constants.java b/src/test/com/cryptomorin/xseries/test/Constants.java
index 4fcf68f..727516d 100644
--- a/src/test/com/cryptomorin/xseries/test/Constants.java
+++ b/src/test/com/cryptomorin/xseries/test/Constants.java
@@ -23,6 +23,7 @@
package com.cryptomorin.xseries.test;
import com.cryptomorin.xseries.reflection.XReflection;
+import com.cryptomorin.xseries.test.util.XLogger;
import org.bukkit.Bukkit;
import org.bukkit.World;
@@ -33,13 +34,23 @@ public final class Constants {
private Constants() {}
public static final Object LOCK = new Object();
- public static final Path DESKTOP = Paths.get(System.getProperty("user.home") + "/Desktop/");
+
+ @SuppressWarnings("ConstantValue")
+ public static Path getTestPath() {
+ // It's inside "XSeries\target\tests" folder.
+ XLogger.log("System test path is " + System.getProperty("user.dir"));
+ if (Bukkit.getServer() == null) {
+ return Paths.get(System.getProperty("user.dir"));
+ } else {
+ return Bukkit.getWorldContainer().toPath();
+ }
+ }
/**
* This sends unnecessary requests to Mojang and also delays out work too,
* so let's not test when it's not needed.
*/
- public static final boolean TEST_MOJANG_API = true;
+ public static final boolean TEST_MOJANG_API = false;
public static final boolean TEST_MOJANG_API_BULK = false;
diff --git a/src/test/com/cryptomorin/xseries/test/XSeriesTests.java b/src/test/com/cryptomorin/xseries/test/XSeriesTests.java
index caeacdf..fb7e1f9 100644
--- a/src/test/com/cryptomorin/xseries/test/XSeriesTests.java
+++ b/src/test/com/cryptomorin/xseries/test/XSeriesTests.java
@@ -73,7 +73,7 @@ public final class XSeriesTests {
public void enumToRegistry() throws URISyntaxException {
URL resource = XSeriesTests.class.getResource("XEnchantment.java");
Path path = Paths.get(resource.toURI());
- ClassConverter.enumToRegistry(path, Constants.DESKTOP);
+ ClassConverter.enumToRegistry(path, Constants.getTestPath());
}
public static void test() {
@@ -225,6 +225,17 @@ private static void testXPotion() {
assertNotNull(XPotion.of(potionType), () -> "null for (Bukkit -> XForm): " + potionType);
assertPresent(XPotion.of(bukkitName), "null for (String -> XForm): " + bukkitName);
}
+
+ assertPotionEffect(XPotion.parseEffect("STRENGTH, 10, 3"), XPotion.STRENGTH, 10, 3);
+ assertPotionEffect(XPotion.parseEffect("BLINDNESS, 30, 1"), XPotion.BLINDNESS, 30, 1);
+ assertPotionEffect(XPotion.parseEffect("SLOWNESS, 200, 10, %75"), XPotion.SLOWNESS, 200, 10);
+ }
+
+ private static void assertPotionEffect(XPotion.Effect effect, XPotion type, int duration, int amplifier) {
+ assertNotNull(effect, "Effect could not be parsed");
+ assertEquals(type, effect.getXPotion(), "Potion effect types don't match");
+ assertEquals(amplifier - 1, effect.getEffect().getAmplifier(), "Potion effect amplifiers don't match");
+ assertEquals(duration * 20, effect.getEffect().getDuration(), "Potion effect durations don't match");
}
private static void testXEnchantment() {
diff --git a/src/test/com/cryptomorin/xseries/test/benchmark/reflection/ReflectionBenchmarkTargetMethodProxy.java b/src/test/com/cryptomorin/xseries/test/benchmark/reflection/ReflectionBenchmarkTargetMethodProxy.java
index de2520b..e3415c5 100644
--- a/src/test/com/cryptomorin/xseries/test/benchmark/reflection/ReflectionBenchmarkTargetMethodProxy.java
+++ b/src/test/com/cryptomorin/xseries/test/benchmark/reflection/ReflectionBenchmarkTargetMethodProxy.java
@@ -23,14 +23,14 @@
package com.cryptomorin.xseries.test.benchmark.reflection;
import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxyObject;
-import com.cryptomorin.xseries.reflection.proxy.annotations.Class;
import com.cryptomorin.xseries.reflection.proxy.annotations.Private;
+import com.cryptomorin.xseries.reflection.proxy.annotations.Proxify;
import com.cryptomorin.xseries.reflection.proxy.annotations.ReflectName;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
-@Class(packageName = "com.cryptomorin.xseries.test.benchmark.reflection", ignoreCurrentName = true)
+@Proxify(packageName = "com.cryptomorin.xseries.test.benchmark.reflection", ignoreCurrentName = true)
@ReflectName("ReflectionBenchmarkTargetMethod")
public interface ReflectionBenchmarkTargetMethodProxy extends ReflectiveProxyObject {
@Private
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMGeneratedSample.java b/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMGeneratedSample.java
index 884c814..d5f36de 100644
--- a/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMGeneratedSample.java
+++ b/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMGeneratedSample.java
@@ -23,6 +23,8 @@
package com.cryptomorin.xseries.test.reflection.asm;
import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxyObject;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -71,6 +73,16 @@ public ASMGeneratedSample instance() {
return (ASMGeneratedSample) instance;
}
+ @Override
+ public @NotNull Class> getTargetClass() {
+ return null;
+ }
+
+ @Override
+ public @NotNull boolean isInstance(@Nullable Object object) {
+ return false;
+ }
+
private Object doubleCtor() {
return new ASMGeneratedSample(new StringBuilder(3));
}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMTests.java b/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMTests.java
index a1ff5e8..c7930bb 100644
--- a/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMTests.java
+++ b/src/test/com/cryptomorin/xseries/test/reflection/asm/ASMTests.java
@@ -22,7 +22,9 @@
package com.cryptomorin.xseries.test.reflection.asm;
+import com.cryptomorin.xseries.reflection.XReflection;
import com.cryptomorin.xseries.reflection.asm.XReflectASM;
+import com.cryptomorin.xseries.test.Constants;
import com.cryptomorin.xseries.test.reflection.proxy.ProxyTestProxified;
import com.cryptomorin.xseries.test.reflection.proxy.ProxyTests;
import com.cryptomorin.xseries.test.util.XLogger;
@@ -30,14 +32,13 @@
public final class ASMTests {
public static void test() {
XLogger.log("[ASM] Testing XReflectASM generation...");
- // XReflectASM asm = XReflectASM.proxify(ServerLevel.class);
- // asm.verify(false);
- // asm.writeToFile(Constants.DESKTOP);
XReflectASM asm = XReflectASM.proxify(ProxyTestProxified.class);
+ asm.writeToFile(Constants.getTestPath());
ProxyTestProxified factoryInstance = asm.create();
ProxyTests.normalProxyTest(factoryInstance);
- ProxyTests.minecraftProxyTest((clazz) -> XReflectASM.proxify(clazz).create());
+ if (XReflection.supports(20))
+ ProxyTests.minecraftProxyTest((clazz) -> XReflectASM.proxify(clazz).create());
}
}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestClass.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestClass.java
index 3eb15a8..07f97e7 100644
--- a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestClass.java
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestClass.java
@@ -22,9 +22,14 @@
package com.cryptomorin.xseries.test.reflection.proxy;
+import com.cryptomorin.xseries.reflection.jvm.objects.ReflectedObject;
+import org.jetbrains.annotations.NotNull;
+
+@TestAnnotation(reference = String.class, filter = true, type = ReflectedObject.Type.FIELD)
+@TestAnnotation2({100, 200})
public class ProxyTestClass {
public static final int finalId = 555;
- public static int id = 555;
+ public static int id = 500;
public int date;
private String operationField;
@@ -37,11 +42,20 @@ public ProxyTestClass(String operationField, int date) {
this.date = date;
}
- private ProxyTestClass(int date) {
+ protected static boolean isBeyond555() {
+ return id > 555;
+ }
+
+ @Deprecated
+ @TestAnnotation(values = {@TestAnnotation2(3), @TestAnnotation2({4, 10})})
+ @TestAnnotation(index = 1)
+ @TestAnnotation(name = "toaster")
+ @TestAnnotation(reference = String.class, filter = true, type = ReflectedObject.Type.FIELD)
+ private ProxyTestClass(@TestAnnotation(index = 3) int date) {
this("OperationPrimus", 2027);
}
- public int compareTo(ProxyTestClass other) {
+ public int compareTo(@Deprecated @NotNull ProxyTestClass other) {
return Integer.compare(this.date, other.date);
}
@@ -55,7 +69,7 @@ public static StringBuilder doStaticThings(int times) {
@SuppressWarnings("MethodMayBeStatic")
private StringBuilder doSomethingPrivate(int length) {
- return new StringBuilder(length);
+ return new StringBuilder(length).append(operationField).append(finalId);
}
public void doSomething(String add, boolean add2) {
@@ -70,7 +84,10 @@ public String getSomething(String add, short add2) {
return add + add2;
}
- public String getSomething(String add) {
+ public String getSomething(String add) throws IllegalArgumentException, IllegalStateException {
+ if ("!".equals(add)) throw new IllegalArgumentException("Invalid argument: " + add);
+ if (operationField == null) throw new IllegalStateException("Operation is not set: " + add);
+
return add + add;
}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProcessor.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProcessor.java
new file mode 100644
index 0000000..75d4379
--- /dev/null
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProcessor.java
@@ -0,0 +1,37 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Crypto Morin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.cryptomorin.xseries.test.reflection.proxy;
+
+public abstract class ProxyTestProcessor {
+ private int processorCount = 4;
+
+ public abstract O process(I first, I second);
+
+ public int getProcessorCount() {
+ return processorCount;
+ }
+
+ public void setProcessorCount(int processorCount) {
+ this.processorCount = processorCount;
+ }
+}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProxified.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProxified.java
index 1ad1278..e9b2834 100644
--- a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProxified.java
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTestProxified.java
@@ -23,11 +23,10 @@
package com.cryptomorin.xseries.test.reflection.proxy;
import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxyObject;
-import com.cryptomorin.xseries.reflection.proxy.annotations.Class;
import com.cryptomorin.xseries.reflection.proxy.annotations.*;
import org.jetbrains.annotations.NotNull;
-@Class(target = ProxyTestClass.class)
+@Proxify(target = ProxyTestClass.class)
public interface ProxyTestProxified extends ReflectiveProxyObject {
// Fields
@Static
@@ -39,6 +38,14 @@ public interface ProxyTestProxified extends ReflectiveProxyObject {
@Field
int id();
+ @Static
+ @Field
+ void id(int newValue);
+
+ @Protected
+ @Static
+ boolean isBeyond555();
+
@Field
int date();
@@ -46,6 +53,10 @@ public interface ProxyTestProxified extends ReflectiveProxyObject {
@Field
String operationField();
+ @Private
+ @Field
+ void operationField(String value);
+
@Static
StringBuilder doStaticThings(int times);
@@ -75,7 +86,7 @@ public interface ProxyTestProxified extends ReflectiveProxyObject {
String getSomething(String add, short add2);
- String getSomething(String add);
+ String getSomething(String add) throws IllegalArgumentException, IllegalStateException;
int getSomething(String add, int add2);
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTests.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTests.java
index 2f9eff1..e8cce2f 100644
--- a/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTests.java
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/ProxyTests.java
@@ -25,6 +25,7 @@
import com.cryptomorin.xseries.reflection.XReflection;
import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxy;
import com.cryptomorin.xseries.reflection.proxy.ReflectiveProxyObject;
+import com.cryptomorin.xseries.reflection.proxy.generator.XProxifier;
import com.cryptomorin.xseries.test.Constants;
import com.cryptomorin.xseries.test.reflection.proxy.minecraft.BlockPos;
import com.cryptomorin.xseries.test.reflection.proxy.minecraft.CraftWorld;
@@ -40,33 +41,66 @@
public final class ProxyTests {
public static void test() {
XLogger.log("[Proxy] Testing ReflectiveProxy generation...");
- normalProxyTest(XReflection.proxify(ProxyTestProxified.class));
+ normalProxyTest(ReflectiveProxy.proxify(ProxyTestProxified.class).proxy());
if (XReflection.supports(20)) minecraftProxyTest((x) -> ReflectiveProxy.proxify(x).proxy());
+ new XProxifier(ProxyTestClass.class).writeTo(Constants.getTestPath());
+ // new XProxifier(
+ // XReflection.ofMinecraft().inPackage(MinecraftPackage.NMS, "server.level")
+ // .map(MinecraftMapping.MOJANG, "ServerPlayer")
+ // .map(MinecraftMapping.SPIGOT, "EntityPlayer")
+ // .map(MinecraftMapping.OBFUSCATED, "are")
+ // .unreflect()
+ // ).writeTo(Constants.getTestPath());
+
testCreateLambda();
testCreateXReflectionLambda();
}
public static void normalProxyTest(ProxyTestProxified factoryProxy) {
+ assertSame(factoryProxy.getTargetClass(), ProxyTestClass.class);
+
+ // Final member tests.
+ int initialValue = ProxyTestClass.id;
assertEquals(ProxyTestClass.finalId, factoryProxy.finalId());
assertEquals(ProxyTestClass.finalId, factoryProxy.finalId());
assertEquals(ProxyTestClass.finalId, factoryProxy.finalId());
assertEquals(ProxyTestClass.id, factoryProxy.id());
+ assertFalse(factoryProxy.isBeyond555());
+ factoryProxy.id(777);
+ assertEquals(777, factoryProxy.id());
+ assertTrue(factoryProxy.isBeyond555());
+ factoryProxy.id(initialValue); // We don't want other tests to fail
assertEquals("0123456789", factoryProxy.doStaticThings(10).toString());
ProxyTestClass instance = factoryProxy.ProxyTestProxified("OperationTestum", 2025);
ProxyTestProxified unusInstance = factoryProxy.bindTo(instance);
+ // isInstance() test
+ assertTrue(factoryProxy.isInstance(instance));
+ assertTrue(factoryProxy.isInstance(unusInstance.instance()));
+ assertFalse(factoryProxy.isInstance(unusInstance));
+
+ // First instance member tests
assertEquals("OperationTestum", unusInstance.operationField());
assertEquals(2025, unusInstance.date());
assertEquals("OperationTestum12false", unusInstance.getSomething("12", false));
unusInstance.iForgotTheName("20", true);
assertEquals("OperationTestumdoSomething20true", unusInstance.operationField());
+ // noinspection StringBufferReplaceableByString
+ assertEquals(
+ new StringBuilder(10).append(unusInstance.operationField()).append(factoryProxy.finalId()).toString(),
+ unusInstance.doSomethingPrivate(10).toString()
+ );
+ unusInstance.operationField("SomeValue");
+ assertEquals("SomeValue", unusInstance.operationField());
// Cannot invoke constructor twice
assertThrows(Exception.class, () -> unusInstance.ProxyTestProxified("OperationDuoTestum"));
+ // Second instance member tests
ProxyTestProxified duoInstance = factoryProxy.ProxyTestProxified("OperationDuoTestum");
+ assertTrue(factoryProxy.isInstance(duoInstance.instance()));
assertEquals("0123456789", factoryProxy.doStaticThings(10).toString());
assertEquals("OperationDuoTestum", duoInstance.operationField());
assertEquals("soosoo", duoInstance.getSomething("soo"));
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation.java
new file mode 100644
index 0000000..6edb939
--- /dev/null
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation.java
@@ -0,0 +1,44 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Crypto Morin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.cryptomorin.xseries.test.reflection.proxy;
+
+import com.cryptomorin.xseries.reflection.jvm.objects.ReflectedObject;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE_PARAMETER, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(TestAnnotationList.class)
+public @interface TestAnnotation {
+ String name() default "";
+
+ int index() default -1;
+
+ boolean filter() default false;
+
+ Class> reference() default void.class;
+
+ ReflectedObject.Type type() default ReflectedObject.Type.CONSTRUCTOR;
+
+ TestAnnotation2[] values() default {};
+}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation2.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation2.java
new file mode 100644
index 0000000..8be06bb
--- /dev/null
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotation2.java
@@ -0,0 +1,34 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Crypto Morin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.cryptomorin.xseries.test.reflection.proxy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE_PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestAnnotation2 {
+ int[] value();
+}
diff --git a/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotationList.java b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotationList.java
new file mode 100644
index 0000000..abfd316
--- /dev/null
+++ b/src/test/com/cryptomorin/xseries/test/reflection/proxy/TestAnnotationList.java
@@ -0,0 +1,34 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Crypto Morin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.cryptomorin.xseries.test.reflection.proxy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE_PARAMETER, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestAnnotationList {
+ TestAnnotation[] value();
+}
diff --git a/src/test/com/cryptomorin/xseries/test/writer/DifferenceHelper.java b/src/test/com/cryptomorin/xseries/test/writer/DifferenceHelper.java
index 520d952..e75fb68 100644
--- a/src/test/com/cryptomorin/xseries/test/writer/DifferenceHelper.java
+++ b/src/test/com/cryptomorin/xseries/test/writer/DifferenceHelper.java
@@ -28,8 +28,8 @@
import com.cryptomorin.xseries.particles.XParticle;
import com.cryptomorin.xseries.reflection.XReflection;
import com.cryptomorin.xseries.reflection.minecraft.MinecraftPackage;
+import com.cryptomorin.xseries.test.Constants;
import com.cryptomorin.xseries.test.util.XLogger;
-import org.bukkit.Bukkit;
import org.bukkit.Keyed;
import org.bukkit.Particle;
import org.bukkit.Sound;
@@ -62,7 +62,7 @@ public final class DifferenceHelper {
* Writes the material and sound differences to files in the server's root folder for updating purposes.
*/
public static void versionDifference() {
- Path serverFolder = Bukkit.getWorldContainer().toPath();
+ Path serverFolder = Constants.getTestPath();
XLogger.log("Server container: " + serverFolder.toAbsolutePath());
Path materials = serverFolder.resolve("XMaterial.txt"),
diff --git a/src/test/resources/server.properties b/src/test/resources/server.properties
index 4b0aa74..9ad5eef 100644
--- a/src/test/resources/server.properties
+++ b/src/test/resources/server.properties
@@ -23,7 +23,7 @@ view-distance=10
server-ip=
resource-pack-prompt=
allow-nether=false
-server-port=25565
+server-port=25566
enable-rcon=false
sync-chunk-writes=true
op-permission-level=4
@@ -49,4 +49,4 @@ spawn-monsters=true
enforce-whitelist=false
resource-pack-sha1=
spawn-protection=16
-max-world-size=29999984
+max-world-size=1000