diff --git a/build.gradle b/build.gradle
index 873a258..2bdb385 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ plugins {
group 'io.github.notstirred'
archivesBaseName = "dasm"
-version '2.5.2'
+version '2.5.3'
ext.ossrhUsername = project.findProperty("ossrhUsername") ?: "" // Enter in ~/.gradle/gradle.properties, not here.
ext.ossrhPassword = project.findProperty("ossrhPassword") ?: "" // Enter in ~/.gradle/gradle.properties, not here.
diff --git a/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java b/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java
new file mode 100644
index 0000000..bbb5fe1
--- /dev/null
+++ b/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java
@@ -0,0 +1,71 @@
+package io.github.notstirred.dasm.api.annotations.redirect.redirects;
+
+import io.github.notstirred.dasm.api.annotations.Dasm;
+import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer;
+import io.github.notstirred.dasm.api.annotations.redirect.sets.IntraOwnerContainer;
+import io.github.notstirred.dasm.api.annotations.selector.FieldSig;
+import io.github.notstirred.dasm.api.annotations.selector.Ref;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * May be added to a field within a {@link Dasm} class.
+ *
+ * Add a {@link FieldToMethodRedirect} to multiple containers inline at method definition, rather than duplicating the
+ * method definition inside the containers.
+ *
+ *
Important notes:
+ * See {@link FieldToMethodRedirect}'s important notes for more information.
+ * Example:
+ * This adds a {@link FieldToMethodRedirect} from {@code barX} -> {@code int getBarX()} and {@code void setBarX(int)} to {@code Bar_redirects} which is an {@link IntraOwnerContainer}
+ * {@code
+ * @Dasm
+ * public class Foo {
+ * private int foo = 0;
+ *
+ * @AddFieldToMethodToSets(
+ * containers = Bar_redirects.class,
+ * field = @FieldSig(type = int.class, name = "barX"),
+ * setter = "setBarX"
+ * )
+ * public int getBarX() {
+ * return this.foo; // This method gets called for every replaced read
+ * }
+ * public void setBarX(int barX) {
+ * this.foo = barX; // This method gets called for every replaced write
+ * }
+ * }
+ * }
+ */
+@Target(METHOD)
+@Retention(CLASS)
+public @interface AddFieldToMethodToSets {
+ /**
+ * The containers to add the {@link FieldToMethodRedirect} to.
+ * Valid container classes are {@link TypeRedirect}, {@link InterOwnerContainer}, {@link IntraOwnerContainer}
+ */
+ Class>[] containers();
+
+ /**
+ * The field to replace
+ */
+ FieldSig field();
+
+ /**
+ * The name of the setter method, must have the same owner (be in the same class) as the getter method
+ * The argument will be inferred from the field and getter.
+ */
+ String setter() default "";
+
+ /**
+ * The source field's mapping owner.
+ *
+ * Only useful if the codebase is remapped and method/field owners are moved
+ * Allows specifying the class which owns the field in the mappings
+ */
+ Ref mappingsOwner() default @Ref;
+}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java
index e29143e..a5aff30 100644
--- a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java
+++ b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java
@@ -3,9 +3,11 @@
import io.github.notstirred.dasm.annotation.parse.MethodSigImpl;
import io.github.notstirred.dasm.annotation.parse.RedirectSetImpl;
import io.github.notstirred.dasm.annotation.parse.RefImpl;
+import io.github.notstirred.dasm.annotation.parse.addtosets.AddFieldToMethodToSetsImpl;
import io.github.notstirred.dasm.annotation.parse.addtosets.AddFieldToSetsImpl;
import io.github.notstirred.dasm.annotation.parse.addtosets.AddMethodToSetsImpl;
import io.github.notstirred.dasm.annotation.parse.redirects.FieldRedirectImpl;
+import io.github.notstirred.dasm.annotation.parse.redirects.FieldToMethodRedirectImpl;
import io.github.notstirred.dasm.annotation.parse.redirects.MethodRedirectImpl;
import io.github.notstirred.dasm.api.annotations.Dasm;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets;
@@ -34,6 +36,7 @@
import java.util.*;
import static io.github.notstirred.dasm.annotation.AnnotationUtil.*;
+import static io.github.notstirred.dasm.util.Format.formatObjectType;
public class AnnotationParser {
private final ClassNodeProvider provider;
@@ -82,77 +85,100 @@ public NotifyStack findDasmAnnotations(ClassNode targetClass) {
Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name));
boolean isTargetInterface = (targetClass.access & Opcodes.ACC_INTERFACE) != 0;
- try (NotifyStack classExceptions = NotifyStack.of(targetClass)) {
-
- findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, Dasm.class, "value", classExceptions);
- findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, TransformFromClass.class, "sets", classExceptions);
-
- for (FieldNode fieldNode : targetClass.fields) {
- try (NotifyStack fieldExceptions = classExceptions.push(fieldNode)) {
- findOuterRedirectSetsForAnnotation(fieldNode.invisibleAnnotations, AddFieldToSets.class, "containers", fieldExceptions);
-
- try {
- Optional optAddToSets = AddFieldToSetsImpl.parse(targetClassType, fieldNode);
- if (optAddToSets.isPresent()) {
- AddFieldToSetsImpl addToSets = optAddToSets.get();
- // All containers for this method must already exist, so we can just use the map
- for (Type containerType : addToSets.containers()) {
- RedirectSetImpl.Container container = this.containers.get(containerType);
- if (container == null) {
- fieldExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType));
- continue;
- }
- FieldRedirectImpl fieldRedirect = new FieldRedirectImpl(
- new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()),
- addToSets.dstOwner(),
- addToSets.dstMethodName()
- );
- container.fieldRedirects().add(fieldRedirect);
- }
+ NotifyStack classExceptions = NotifyStack.of(targetClass);
+
+ findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, Dasm.class, "value", classExceptions);
+ findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, TransformFromClass.class, "sets", classExceptions);
+
+ for (FieldNode fieldNode : targetClass.fields) {
+ NotifyStack fieldExceptions = classExceptions.push(fieldNode);
+ findOuterRedirectSetsForAnnotation(fieldNode.invisibleAnnotations, AddFieldToSets.class, "containers", fieldExceptions);
+
+ try {
+ Optional optAddToSets = AddFieldToSetsImpl.parse(targetClassType, fieldNode);
+ if (optAddToSets.isPresent()) {
+ AddFieldToSetsImpl addToSets = optAddToSets.get();
+ // All containers for this method must already exist, so we can just use the map
+ for (Type containerType : addToSets.containers()) {
+ RedirectSetImpl.Container container = this.containers.get(containerType);
+ if (container == null) {
+ fieldExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType));
+ continue;
+ }
+ FieldRedirectImpl fieldRedirect = new FieldRedirectImpl(
+ new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()),
+ addToSets.dstOwner(),
+ addToSets.dstFieldName()
+ );
+ container.fieldRedirects().add(fieldRedirect);
+ }
+ }
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ fieldExceptions.notifyFromException(e);
+ }
+ }
+
+ for (MethodNode methodNode : targetClass.methods) {
+ NotifyStack methodExceptions = classExceptions.push(methodNode);
+ findRedirectSetsForAnnotation(methodNode.invisibleAnnotations, TransformFromMethod.class, "useRedirectSets", methodExceptions);
+ findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddTransformToSets.class, "value", methodExceptions);
+ findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddMethodToSets.class, "containers", methodExceptions);
+
+ try {
+ Optional optAddToSets = AddMethodToSetsImpl.parse(targetClassType, isTargetInterface, methodNode);
+ if (optAddToSets.isPresent()) {
+ AddMethodToSetsImpl addToSets = optAddToSets.get();
+ // All containers for this method must already exist, so we can just use the map
+ for (Type containerType : addToSets.containers()) {
+ RedirectSetImpl.Container container = this.containers.get(containerType);
+ if (container == null) {
+ methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType));
+ continue;
}
- } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
- MethodSigImpl.EmptySrcName e) {
- fieldExceptions.notifyFromException(e);
+
+ MethodRedirectImpl methodRedirect = new MethodRedirectImpl(
+ new ClassMethod(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcMethod()),
+ addToSets.dstOwner(),
+ addToSets.dstMethodName(),
+ addToSets.isStatic(),
+ addToSets.isDstInterface()
+ );
+ container.methodRedirects().add(methodRedirect);
}
}
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ methodExceptions.notifyFromException(e);
}
- for (MethodNode methodNode : targetClass.methods) {
- try (NotifyStack methodExceptions = classExceptions.push(methodNode)) {
- findRedirectSetsForAnnotation(methodNode.invisibleAnnotations, TransformFromMethod.class, "useRedirectSets", methodExceptions);
- findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddTransformToSets.class, "value", methodExceptions);
- findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddMethodToSets.class, "containers", methodExceptions);
-
- try {
- Optional optAddToSets = AddMethodToSetsImpl.parse(targetClassType, isTargetInterface, methodNode);
- if (optAddToSets.isPresent()) {
- AddMethodToSetsImpl addToSets = optAddToSets.get();
- // All containers for this method must already exist, so we can just use the map
- for (Type containerType : addToSets.containers()) {
- RedirectSetImpl.Container container = this.containers.get(containerType);
- if (container == null) {
- methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType));
- continue;
- }
-
- MethodRedirectImpl methodRedirect = new MethodRedirectImpl(
- new ClassMethod(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcMethod()),
- addToSets.dstOwner(),
- addToSets.dstMethodName(),
- addToSets.isStatic(),
- addToSets.isDstInterface()
- );
- container.methodRedirects().add(methodRedirect);
- }
+ try {
+ Optional optAddToSets = AddFieldToMethodToSetsImpl.parse(targetClassType, methodNode);
+ if (optAddToSets.isPresent()) {
+ AddFieldToMethodToSetsImpl addToSets = optAddToSets.get();
+ // All containers for this method must already exist, so we can just use the map
+ for (Type containerType : addToSets.containers()) {
+ RedirectSetImpl.Container container = this.containers.get(containerType);
+ if (container == null) {
+ methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType));
+ continue;
}
- } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
- MethodSigImpl.EmptySrcName e) {
- methodExceptions.notifyFromException(e);
+ FieldToMethodRedirectImpl fieldToMethodRedirect = new FieldToMethodRedirectImpl(
+ new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()),
+ addToSets.dstMethod(),
+ addToSets.dstSetterMethod(),
+ addToSets.isStatic(),
+ (targetClass.access & Opcodes.ACC_INTERFACE) != 0
+ );
+ container.fieldToMethodRedirects().add(fieldToMethodRedirect);
}
}
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ methodExceptions.notifyFromException(e);
}
- return classExceptions;
}
+ return classExceptions;
}
private void findRedirectSetsForAnnotation(List annotations, Class> annotationClass, String setsAnnotationField,
@@ -262,19 +288,19 @@ private ClassNode getContainingRedirectSetClassNode(Type containerType) throws N
public static class ContainerNotWithinRedirectSet extends DasmException {
public ContainerNotWithinRedirectSet(Type containerType) {
- super(String.format("Container `" + containerType.getClassName() + "` must be within a @RedirectSet interface"));
+ super(String.format("Container `" + formatObjectType(containerType) + "` must be within a @RedirectSet interface"));
}
}
public static class TypeIsNotAContainer extends DasmException {
public TypeIsNotAContainer(Type type) {
- super(String.format("Type `" + type.getClassName() + "` is not a container but is used as one"));
+ super(String.format("Type `" + formatObjectType(type) + "` is not a container but is used as one"));
}
}
public static class NoValidRedirectSetExists extends Notification {
public NoValidRedirectSetExists(Type redirectSetType) {
- super(String.format("No valid redirect set exists matching `" + redirectSetType.getClassName() + "`"));
+ super(String.format("No valid redirect set exists matching `" + formatObjectType(redirectSetType) + "`"));
}
}
}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java
index a33154e..b72ff37 100644
--- a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java
+++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java
@@ -18,7 +18,6 @@
import java.util.stream.Stream;
import static io.github.notstirred.dasm.annotation.AnnotationUtil.getAnnotationIfPresent;
-import static io.github.notstirred.dasm.util.TypeUtil.simpleClassNameOf;
import static io.github.notstirred.dasm.util.Util.atLeastTwoOf;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
@@ -55,68 +54,67 @@ public enum Kind {
public static Optional parse(ClassNode redirectSetClassNode, ClassNodeProvider provider, NotifyStack exceptions) {
List superRedirectSets = new ArrayList<>();
- try (NotifyStack redirectSetExceptions = exceptions.push(redirectSetClassNode)) {
+ NotifyStack redirectSetExceptions = exceptions.push(redirectSetClassNode);
- AnnotationNode annotationNode = getAnnotationIfPresent(redirectSetClassNode.invisibleAnnotations, RedirectSet.class);
- if (annotationNode == null) {
- redirectSetExceptions.notify(new MissingRedirectSetAnnotationException(Type.getObjectType(redirectSetClassNode.name)));
- }
+ AnnotationNode annotationNode = getAnnotationIfPresent(redirectSetClassNode.invisibleAnnotations, RedirectSet.class);
+ if (annotationNode == null) {
+ redirectSetExceptions.notify(new MissingRedirectSetAnnotationException(Type.getObjectType(redirectSetClassNode.name)));
+ }
- if ((redirectSetClassNode.access & ACC_INTERFACE) == 0) {
- redirectSetExceptions.notify(new NonInterfaceIsUsedAsRedirectSetException(Type.getObjectType(redirectSetClassNode.name)));
- }
+ if ((redirectSetClassNode.access & ACC_INTERFACE) == 0) {
+ redirectSetExceptions.notify(new NonInterfaceIsUsedAsRedirectSetException(Type.getObjectType(redirectSetClassNode.name)));
+ }
+
+ // Add inherited redirect sets
+ for (String itf : redirectSetClassNode.interfaces) {
+ superRedirectSets.add(Type.getObjectType(itf));
+ }
+
+ Map innerClassData = new HashMap<>();
- // Add inherited redirect sets
- for (String itf : redirectSetClassNode.interfaces) {
- superRedirectSets.add(Type.getObjectType(itf));
+ // Discover type/field/method redirects in innerClass
+ for (InnerClassNode innerClass : redirectSetClassNode.innerClasses) {
+ if (!innerClass.outerName.equals(redirectSetClassNode.name) || innerClass.name.equals(redirectSetClassNode.name)) {
+ // `innerClasses` contains a list of all inner classes of the root class, exclude any not a direct child
+ // also seems to contain the outer class too.
+ continue;
}
- Map innerClassData = new HashMap<>();
+ ClassNode innerClassNode;
+ try {
+ innerClassNode = provider.classNode(Type.getObjectType(innerClass.name));
+ } catch (NoSuchTypeExists e) {
+ redirectSetExceptions.notifyFromException(e);
+ // The inner class doesn't exist, we can't begin parsing it.
+ continue;
+ }
+ NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassNode);
+ parseInnerClass(innerClassNode, innerClassExceptions).ifPresent(superNameContainerPair ->
+ innerClassData.put(superNameContainerPair.container().type(), superNameContainerPair)
+ );
+ }
- // Discover type/field/method redirects in innerClass
- for (InnerClassNode innerClass : redirectSetClassNode.innerClasses) {
- if (!innerClass.outerName.equals(redirectSetClassNode.name) || innerClass.name.equals(redirectSetClassNode.name)) {
- // `innerClasses` contains a list of all inner classes of the root class, exclude any not a direct child
- // also seems to contain the outer class too.
- continue;
+ innerClassData.values().forEach(innerClassInfo -> {
+ NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassInfo.container.type);
+ innerClassInfo.superNames.forEach(superName -> {
+ Type superContainerType = Type.getObjectType(superName);
+ if (superContainerType.equals(Type.getType(Object.class))) { // having no super type is always OK
+ return;
}
-
- ClassNode innerClassNode;
- try {
- innerClassNode = provider.classNode(Type.getObjectType(innerClass.name));
- } catch (NoSuchTypeExists e) {
- redirectSetExceptions.notifyFromException(e);
- // The inner class doesn't exist, we can't begin parsing it.
- continue;
+ InnerClassInfo superContainerInfo = innerClassData.get(superContainerType);
+ if (superContainerInfo == null) {
+ innerClassExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName()));
+ return;
}
- try (NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassNode)) {
- parseInnerClass(innerClassNode, innerClassExceptions).ifPresent(superNameContainerPair ->
- innerClassData.put(superNameContainerPair.container().type(), superNameContainerPair)
- );
+ if (innerClassInfo.container.superContainer != null) {
+ innerClassExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container));
+ return;
}
- }
-
- innerClassData.values().forEach(innerClassInfo -> {
- innerClassInfo.superNames.forEach(superName -> {
- Type superContainerType = Type.getObjectType(superName);
- if (superContainerType.equals(Type.getType(Object.class))) { // having no super type is always OK
- return;
- }
- InnerClassInfo superContainerInfo = innerClassData.get(superContainerType);
- if (superContainerInfo == null) {
- redirectSetExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName()));
- return;
- }
- if (innerClassInfo.container.superContainer != null) {
- redirectSetExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container));
- return;
- }
- innerClassInfo.container.superContainer = superContainerInfo.container();
- });
+ innerClassInfo.container.superContainer = superContainerInfo.container();
});
+ });
- return Optional.of(new RedirectSetImpl(superRedirectSets, innerClassData.values().stream().map(InnerClassInfo::container).collect(Collectors.toList())));
- }
+ return Optional.of(new RedirectSetImpl(superRedirectSets, innerClassData.values().stream().map(InnerClassInfo::container).collect(Collectors.toList())));
}
@Data
@@ -216,17 +214,16 @@ private static Optional parseInnerClass(ClassNode innerClassNode
private static void parseFields(ClassNode innerClassNode, Type srcType, Type dstType, Set fieldRedirects,
Set fieldToMethodRedirects, NotifyStack exceptions) {
for (FieldNode fieldNode : innerClassNode.fields) {
- try (NotifyStack fieldExceptions = exceptions.push(fieldNode)) {
- try {
- Optional fieldRedirect = FieldRedirectImpl.parseFieldRedirect(srcType, fieldNode, dstType);
- if (fieldRedirect.isPresent()) {
- fieldRedirects.add(fieldRedirect.get());
- } else {
- fieldExceptions.notify(new FieldMissingFieldRedirectAnnotationException(fieldNode));
- }
- } catch (RefImpl.RefAnnotationGivenNoArguments | FieldRedirectImpl.FieldRedirectHasEmptySrcName e) {
- fieldExceptions.notifyFromException(e);
+ NotifyStack fieldExceptions = exceptions.push(fieldNode);
+ try {
+ Optional fieldRedirect = FieldRedirectImpl.parseFieldRedirect(srcType, fieldNode, dstType);
+ if (fieldRedirect.isPresent()) {
+ fieldRedirects.add(fieldRedirect.get());
+ } else {
+ fieldExceptions.notify(new FieldMissingFieldRedirectAnnotationException(fieldNode));
}
+ } catch (RefImpl.RefAnnotationGivenNoArguments | FieldRedirectImpl.FieldRedirectHasEmptySrcName e) {
+ fieldExceptions.notifyFromException(e);
}
}
}
@@ -239,60 +236,59 @@ private static void parseMethods(ClassNode innerClassNode, Type srcType, Type ds
continue; // Skip default empty constructor
}
- try (NotifyStack methodExceptions = exceptions.push(methodNode)) {
-
- Optional methodRedirect = Optional.empty();
- Optional fieldToMethodRedirect = Optional.empty();
- Optional constructorToFactoryRedirect = Optional.empty();
- try {
- methodRedirect = MethodRedirectImpl.parseMethodRedirect(
- srcType,
- (innerClassNode.access & ACC_INTERFACE) != 0,
- methodNode,
- dstType
- );
- } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
- MethodSigImpl.EmptySrcName e) {
- methodExceptions.notifyFromException(e);
- }
- try {
- fieldToMethodRedirect = FieldToMethodRedirectImpl.parse(
- srcType,
- (innerClassNode.access & ACC_INTERFACE) != 0,
- methodNode,
- dstType
- );
- } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
- MethodSigImpl.EmptySrcName e) {
- methodExceptions.notifyFromException(e);
- }
- try {
- constructorToFactoryRedirect = ConstructorToFactoryRedirectImpl.parse(
- srcType,
- (innerClassNode.access & ACC_INTERFACE) != 0,
- methodNode,
- dstType
- );
- } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
- MethodSigImpl.EmptySrcName e) {
- methodExceptions.notifyFromException(e);
- }
-
- if (atLeastTwoOf(methodRedirect.isPresent(), fieldToMethodRedirect.isPresent(), constructorToFactoryRedirect.isPresent())) {
- // if both are present, add exception and return
- methodExceptions.notify(new MoreThanOneMethodRedirect(methodNode));
- return;
- } else if (!methodRedirect.isPresent() && !fieldToMethodRedirect.isPresent() && !constructorToFactoryRedirect.isPresent()) {
- // if none are present, add exception and return
- methodExceptions.notify(new MissingMethodRedirectRedirect(methodNode));
- return;
- }
+ NotifyStack methodExceptions = exceptions.push(methodNode);
+
+ Optional methodRedirect = Optional.empty();
+ Optional fieldToMethodRedirect = Optional.empty();
+ Optional constructorToFactoryRedirect = Optional.empty();
+ try {
+ methodRedirect = MethodRedirectImpl.parseMethodRedirect(
+ srcType,
+ (innerClassNode.access & ACC_INTERFACE) != 0,
+ methodNode,
+ dstType
+ );
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ methodExceptions.notifyFromException(e);
+ }
+ try {
+ fieldToMethodRedirect = FieldToMethodRedirectImpl.parse(
+ srcType,
+ (innerClassNode.access & ACC_INTERFACE) != 0,
+ methodNode,
+ dstType
+ );
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ methodExceptions.notifyFromException(e);
+ }
+ try {
+ constructorToFactoryRedirect = ConstructorToFactoryRedirectImpl.parse(
+ srcType,
+ (innerClassNode.access & ACC_INTERFACE) != 0,
+ methodNode,
+ dstType
+ );
+ } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature |
+ MethodSigImpl.EmptySrcName e) {
+ methodExceptions.notifyFromException(e);
+ }
- // only one must be present by this point
- methodRedirect.ifPresent(methodRedirects::add);
- fieldToMethodRedirect.ifPresent(fieldToMethodRedirects::add);
- constructorToFactoryRedirect.ifPresent(constructorToFactoryRedirects::add);
+ if (atLeastTwoOf(methodRedirect.isPresent(), fieldToMethodRedirect.isPresent(), constructorToFactoryRedirect.isPresent())) {
+ // if both are present, add exception and return
+ methodExceptions.notify(new MoreThanOneMethodRedirect(methodNode));
+ return;
+ } else if (!methodRedirect.isPresent() && !fieldToMethodRedirect.isPresent() && !constructorToFactoryRedirect.isPresent()) {
+ // if none are present, add exception and return
+ methodExceptions.notify(new MissingMethodRedirectRedirect(methodNode));
+ return;
}
+
+ // only one must be present by this point
+ methodRedirect.ifPresent(methodRedirects::add);
+ fieldToMethodRedirect.ifPresent(fieldToMethodRedirects::add);
+ constructorToFactoryRedirect.ifPresent(constructorToFactoryRedirects::add);
}
}
@@ -304,44 +300,44 @@ public SuperTypeInInvalidRedirectSet(String containerName, String superContainer
public static class InterOwnerContainerHasNonStaticRedirects extends Notification {
public InterOwnerContainerHasNonStaticRedirects(Type type) {
- super("InterOwnerContainer " + simpleClassNameOf(type) + " contains non-static redirects which is invalid." +
+ super("InterOwnerContainer contains non-static redirects which is invalid." +
"Consider using @TypeRedirect instead.");
}
}
public static class MoreThanOneContainerException extends Notification {
public MoreThanOneContainerException(Type type) {
- super(simpleClassNameOf(type) + " has more than one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer");
+ super("Type has more than one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer");
}
}
public static class MissingContainerException extends Notification {
public MissingContainerException(Type redirectSetType) {
- super(simpleClassNameOf(redirectSetType) + " is missing one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer.");
+ super("Type is missing one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer.");
}
}
public static class MissingMethodRedirectRedirect extends Notification {
public MissingMethodRedirectRedirect(MethodNode methodNode) {
- super("Method `" + methodNode.name + "` is missing a @MethodRedirect, @FieldToMethodRedirect or a @ConstructorToFactoryRedirect annotation.");
+ super("Method is missing a @MethodRedirect, @FieldToMethodRedirect or a @ConstructorToFactoryRedirect annotation.");
}
}
public static class MoreThanOneMethodRedirect extends Notification {
public MoreThanOneMethodRedirect(MethodNode methodNode) {
- super("Method `" + methodNode.name + "` has more than one of @MethodRedirect, @FieldToMethodRedirect and @ConstructorToFactoryRedirect annotations.");
+ super("Method has more than one of @MethodRedirect, @FieldToMethodRedirect and @ConstructorToFactoryRedirect annotations.");
}
}
public static class MissingRedirectSetAnnotationException extends Notification {
public MissingRedirectSetAnnotationException(Type redirectSetType) {
- super(simpleClassNameOf(redirectSetType) + " is missing @RedirectSet annotation");
+ super("Type is missing @RedirectSet annotation");
}
}
public static class NonInterfaceIsUsedAsRedirectSetException extends Notification {
public NonInterfaceIsUsedAsRedirectSetException(Type redirectSetType) {
- super("Non-interface " + simpleClassNameOf(redirectSetType) + " is used as a redirect set");
+ super("Non-interface is used as a redirect set");
}
}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java
new file mode 100644
index 0000000..9f1fb42
--- /dev/null
+++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java
@@ -0,0 +1,63 @@
+package io.github.notstirred.dasm.annotation.parse.addtosets;
+
+import io.github.notstirred.dasm.annotation.AnnotationUtil;
+import io.github.notstirred.dasm.annotation.parse.FieldSigImpl;
+import io.github.notstirred.dasm.annotation.parse.MethodSigImpl;
+import io.github.notstirred.dasm.annotation.parse.RefImpl;
+import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToMethodToSets;
+import io.github.notstirred.dasm.data.ClassMethod;
+import io.github.notstirred.dasm.data.Field;
+import lombok.Data;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static io.github.notstirred.dasm.annotation.parse.RefImpl.parseOptionalRefAnnotation;
+import static io.github.notstirred.dasm.annotation.parse.redirects.FieldToMethodRedirectImpl.setterDescriptorFor;
+
+@Data
+public class AddFieldToMethodToSetsImpl {
+ private final List containers;
+
+ private final Field srcField;
+ private final Optional mappingsOwner;
+
+ private final ClassMethod dstMethod;
+ private final Optional dstSetterMethod;
+
+ private final boolean isStatic;
+
+ public static Optional parse(Type dstOwner, MethodNode methodNode)
+ throws RefImpl.RefAnnotationGivenNoArguments, MethodSigImpl.InvalidMethodSignature, MethodSigImpl.EmptySrcName {
+ AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(methodNode.invisibleAnnotations, AddFieldToMethodToSets.class);
+ if (annotation == null) {
+ return Optional.empty();
+ }
+
+ Map values = AnnotationUtil.getAnnotationValues(annotation, AddFieldToMethodToSets.class);
+
+ Field srcField = FieldSigImpl.parse((AnnotationNode) values.get("field"));
+
+ String setter = (String) values.get("setter");
+
+ Optional mappingsOwner = parseOptionalRefAnnotation((AnnotationNode) values.get("mappingsOwner"));
+
+ List sets = (List) values.get("containers");
+
+ Method getter = new Method(methodNode.name, methodNode.desc);
+ return Optional.of(new AddFieldToMethodToSetsImpl(
+ sets,
+ new Field(srcField.type(), srcField.name()),
+ mappingsOwner,
+ new ClassMethod(dstOwner, getter),
+ setter.isEmpty() ? Optional.empty() : Optional.of(new ClassMethod(dstOwner, new Method(setter, setterDescriptorFor(getter)))),
+ (methodNode.access & Opcodes.ACC_STATIC) != 0
+ ));
+ }
+}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java
index e838238..9118fb8 100644
--- a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java
+++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java
@@ -25,11 +25,11 @@ public class AddFieldToSetsImpl {
private final Optional mappingsOwner;
private final Type dstOwner;
- private final String dstMethodName;
+ private final String dstFieldName;
- public static Optional parse(Type dstOwner, FieldNode methodNode)
+ public static Optional parse(Type dstOwner, FieldNode fieldNode)
throws RefImpl.RefAnnotationGivenNoArguments, MethodSigImpl.InvalidMethodSignature, MethodSigImpl.EmptySrcName {
- AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(methodNode.invisibleAnnotations, AddFieldToSets.class);
+ AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(fieldNode.invisibleAnnotations, AddFieldToSets.class);
if (annotation == null) {
return Optional.empty();
}
@@ -47,7 +47,7 @@ public static Optional parse(Type dstOwner, FieldNode method
srcField,
mappingsOwner,
dstOwner,
- methodNode.name
+ fieldNode.name
));
}
}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java
index b3b66da..7167cee 100644
--- a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java
+++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java
@@ -58,7 +58,7 @@ public FieldRedirectHasEmptySrcName(FieldNode fieldNode) {
public static class FieldMissingFieldRedirectAnnotationException extends Notification {
public FieldMissingFieldRedirectAnnotationException(FieldNode fieldNode) {
- super("Field `" + fieldNode.name + "` is missing a @FieldRedirect annotation.");
+ super("Field is missing a @FieldRedirect annotation.");
}
}
}
diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java
index c97d4b7..518d70c 100644
--- a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java
+++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java
@@ -55,7 +55,7 @@ public static Optional parse(Type fieldOwner, boolean
));
}
- private static @NotNull String setterDescriptorFor(Method getter) {
+ public static @NotNull String setterDescriptorFor(Method getter) {
return "(" + getter.getReturnType().getDescriptor() + ")V";
}
}
diff --git a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java
index 35c6a05..decef55 100644
--- a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java
+++ b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java
@@ -37,121 +37,118 @@ public class DasmContext {
public Pair, List> buildClassTarget(ClassNode targetClass) throws DasmException {
Type targetType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name));
- try (NotifyStack classExceptions = NotifyStack.of(targetClass)) {
-
- AnnotationNode transformFromClassNode = getAnnotationIfPresent(targetClass.invisibleAnnotations, TransformFromClass.class);
- if (transformFromClassNode != null) {
- Map values = getAnnotationValues(transformFromClassNode, TransformFromClass.class);
-
- try {
- Type srcType = RefImpl.parseRefAnnotation("value", values);
- ApplicationStage stage = (ApplicationStage) values.get("stage");
-
- List sets = unrollSets(
- AnnotationUtil.annotationElementAsList(values.get("sets")).orElseGet(ArrayList::new).stream()
- .map(this.redirectSetsByType::get)
- .collect(Collectors.toList())
- );
-
- // FIXME: this should verify that there are no method transforms inside this class,
- return new Pair<>(Optional.of(new ClassTransform(srcType, targetType, sets, stage)), classExceptions.notifications());
- } catch (RefImpl.RefAnnotationGivenNoArguments e) {
- classExceptions.notifyFromException(e);
- }
- }
+ NotifyStack classExceptions = NotifyStack.of(targetClass);
+
+ AnnotationNode transformFromClassNode = getAnnotationIfPresent(targetClass.invisibleAnnotations, TransformFromClass.class);
+ if (transformFromClassNode != null) {
+ Map values = getAnnotationValues(transformFromClassNode, TransformFromClass.class);
+
+ try {
+ Type srcType = RefImpl.parseRefAnnotation("value", values);
+ ApplicationStage stage = (ApplicationStage) values.get("stage");
- return new Pair<>(Optional.empty(), classExceptions.notifications());
+ List sets = unrollSets(
+ AnnotationUtil.annotationElementAsList(values.get("sets")).orElseGet(ArrayList::new).stream()
+ .map(this.redirectSetsByType::get)
+ .collect(Collectors.toList())
+ );
+
+ // FIXME: this should verify that there are no method transforms inside this class,
+ return new Pair<>(Optional.of(new ClassTransform(srcType, targetType, sets, stage)), classExceptions.notifications());
+ } catch (RefImpl.RefAnnotationGivenNoArguments e) {
+ classExceptions.notifyFromException(e);
+ }
}
+
+ return new Pair<>(Optional.empty(), classExceptions.notifications());
}
public Pair>, List> buildMethodTargets(ClassNode dasmClass, String methodPrefix) {
boolean isTargetTypeInterface = (dasmClass.access & Opcodes.ACC_INTERFACE) != 0;
- try (NotifyStack classExceptions = NotifyStack.of(dasmClass)) {
-
- AnnotationNode dasmNode = getAnnotationIfPresent(dasmClass.invisibleAnnotations, Dasm.class);
- if (dasmNode != null) {
- Map values = getAnnotationValues(dasmNode, Dasm.class);
- Type targetType = RefImpl.parseOptionalRefAnnotation((AnnotationNode) values.get("target"))
- .map(type -> type.getClassName().equals(Dasm.SELF_TARGET.class.getName()) ? null : type)
- .orElseGet(() -> Type.getType(TypeUtil.typeNameToDescriptor(dasmClass.name)));
-
- @SuppressWarnings("unchecked")
- List defaultRedirectSets = unrollSets(((List) values.get("value")).stream()
- .map(type -> {
- RedirectSetImpl redirectSet = this.redirectSetsByType.get(type);
- if (redirectSet == null) {
- classExceptions.notifyFromException(new NoSuchTypeExists(type));
- }
- return redirectSet;
- }).filter(Objects::nonNull)
- .collect(Collectors.toList())
- );
-
- List methodTransforms = new ArrayList<>();
-
- for (MethodNode method : dasmClass.methods) {
- try (NotifyStack methodExceptions = classExceptions.push(method)) {
-
- TransformMethodImpl transformMethod = parseTransformMethod(method, methodExceptions);
- if (transformMethod == null)
- continue;
-
- Type methodOwner = transformMethod.owner().orElse(targetType);
-
- List redirectSets = transformMethod.overriddenRedirectSets().map(types ->
- types.stream().map(this.redirectSetsByType::get).collect(Collectors.toList()))
- .map(this::unrollSets)
- .orElse(defaultRedirectSets);
-
- // FIXME: figure out if there is a way to avoid this with mixin.
- // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply.
- // We redirect to the non-prefixed name, but create the method with the prefix, for later removal.
- String nonPrefixedMethodName = method.name;
- String prefixedMethodName = methodPrefix + nonPrefixedMethodName;
-
- List addedParameters = getAddedParameters(method, methodExceptions);
-
- Pair visibility = getRequestedVisibility(method, transformMethod.visibility(), methodExceptions);
-
- MethodTransform transform = new MethodTransform(
- new ClassMethod(methodOwner, methodOwner, transformMethod.srcMethod()),
- prefixedMethodName // We have to rename constructors because we add a prefix, and mixin expects that anything with <> is either init, or clinit
- .replace("", "__init__")
- .replace("", "__clinit__"),
- redirectSets,
- transformMethod.stage(),
- transformMethod.inPlace(),
- new MethodTransform.TransformChanges(
- addedParameters,
- visibility.first,
- visibility.second
- ),
- new MethodTransform.OriginalTransformData(targetType.getInternalName(), method)
- );
-
- AnnotationNode addToSetsAnnotation = getAnnotationIfPresent(method.invisibleAnnotations, AddTransformToSets.class);
- if (addToSetsAnnotation != null) {
- Map addToSets = getAnnotationValues(addToSetsAnnotation, AddTransformToSets.class);
- List sets = (List) addToSets.get("value");
-
- sets.forEach(set -> this.containersByType.get(set).methodRedirects().add(new MethodRedirectImpl(
- transform.srcMethod(),
- targetType,
- nonPrefixedMethodName,
- (method.access & ACC_STATIC) != 0,
- isTargetTypeInterface
- )));
+ NotifyStack classExceptions = NotifyStack.of(dasmClass);
+
+ AnnotationNode dasmNode = getAnnotationIfPresent(dasmClass.invisibleAnnotations, Dasm.class);
+ if (dasmNode != null) {
+ Map values = getAnnotationValues(dasmNode, Dasm.class);
+ Type targetType = RefImpl.parseOptionalRefAnnotation((AnnotationNode) values.get("target"))
+ .map(type -> type.getClassName().equals(Dasm.SELF_TARGET.class.getName()) ? null : type)
+ .orElseGet(() -> Type.getType(TypeUtil.typeNameToDescriptor(dasmClass.name)));
+
+ @SuppressWarnings("unchecked")
+ List defaultRedirectSets = unrollSets(((List) values.get("value")).stream()
+ .map(type -> {
+ RedirectSetImpl redirectSet = this.redirectSetsByType.get(type);
+ if (redirectSet == null) {
+ classExceptions.notifyFromException(new NoSuchTypeExists(type));
}
+ return redirectSet;
+ }).filter(Objects::nonNull)
+ .collect(Collectors.toList())
+ );
+
+ List methodTransforms = new ArrayList<>();
+
+ for (MethodNode method : dasmClass.methods) {
+ NotifyStack methodExceptions = classExceptions.push(method);
+
+ TransformMethodImpl transformMethod = parseTransformMethod(method, methodExceptions);
+ if (transformMethod == null)
+ continue;
+
+ Type methodOwner = transformMethod.owner().orElse(targetType);
+
+ List redirectSets = transformMethod.overriddenRedirectSets().map(types ->
+ types.stream().map(this.redirectSetsByType::get).collect(Collectors.toList()))
+ .map(this::unrollSets)
+ .orElse(defaultRedirectSets);
+
+ // FIXME: figure out if there is a way to avoid this with mixin.
+ // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply.
+ // We redirect to the non-prefixed name, but create the method with the prefix, for later removal.
+ String nonPrefixedMethodName = method.name;
+ String prefixedMethodName = methodPrefix + nonPrefixedMethodName;
+
+ List addedParameters = getAddedParameters(method, methodExceptions);
+
+ Pair visibility = getRequestedVisibility(method, transformMethod.visibility(), methodExceptions);
+
+ MethodTransform transform = new MethodTransform(
+ new ClassMethod(methodOwner, methodOwner, transformMethod.srcMethod()),
+ prefixedMethodName // We have to rename constructors because we add a prefix, and mixin expects that anything with <> is either init, or clinit
+ .replace("", "__init__")
+ .replace("", "__clinit__"),
+ redirectSets,
+ transformMethod.stage(),
+ transformMethod.inPlace(),
+ new MethodTransform.TransformChanges(
+ addedParameters,
+ visibility.first,
+ visibility.second
+ ),
+ new MethodTransform.OriginalTransformData(targetType.getInternalName(), method)
+ );
- methodTransforms.add(transform);
- }
+ AnnotationNode addToSetsAnnotation = getAnnotationIfPresent(method.invisibleAnnotations, AddTransformToSets.class);
+ if (addToSetsAnnotation != null) {
+ Map addToSets = getAnnotationValues(addToSetsAnnotation, AddTransformToSets.class);
+ List sets = (List) addToSets.get("value");
+
+ sets.forEach(set -> this.containersByType.get(set).methodRedirects().add(new MethodRedirectImpl(
+ transform.srcMethod(),
+ targetType,
+ nonPrefixedMethodName,
+ (method.access & ACC_STATIC) != 0,
+ isTargetTypeInterface
+ )));
}
- return new Pair<>(Optional.of(methodTransforms), classExceptions.notifications());
- }
- return new Pair<>(Optional.empty(), classExceptions.notifications());
+ methodTransforms.add(transform);
+ }
+ return new Pair<>(Optional.of(methodTransforms), classExceptions.notifications());
}
+
+ return new Pair<>(Optional.empty(), classExceptions.notifications());
}
private Pair getRequestedVisibility(MethodNode method, Visibility annotationVisibility, NotifyStack methodExceptions) {
@@ -235,13 +232,13 @@ private void unrollSetsInner(RedirectSetImpl redirectSet, Collection transform(ClassNode targetClass, Collection transforms) {
- try (NotifyStack dasmClassExceptions = NotifyStack.of(targetClass)) {
-
- Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name));
- for (MethodTransform transform : transforms) {
- try (NotifyStack methodExceptions = dasmClassExceptions.push(transform.originalTransformData().methodNode())) {
- Type methodSrcOwner = transform.srcMethod().owner();
-
- ClassNode srcClass;
- if (methodSrcOwner.equals(targetClassType)) {
- srcClass = targetClass;
- } else {
- try {
- srcClass = this.classNodeProvider.classNode(methodSrcOwner);
- } catch (NoSuchTypeExists e) {
- methodExceptions.notifyFromException(e);
- continue;
- }
- }
+ NotifyStack dasmClassExceptions = NotifyStack.of(targetClass);
- TransformRedirects transformRedirects = new TransformRedirects(transform.redirectSets(), this.mappingsProvider);
- if (transform.inPlace()) {
- applyRedirects(srcClass, transform.srcMethod(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true);
- } else {
- // FIXME: java 8 synthetic accessor methods
- cloneAndApplyRedirects(srcClass, targetClass, transform.srcMethod(), transform.dstMethodName(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true);
- }
+ Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name));
+ for (MethodTransform transform : transforms) {
+ NotifyStack methodExceptions = dasmClassExceptions.push(transform.originalTransformData().methodNode());
+ Type methodSrcOwner = transform.srcMethod().owner();
+
+ ClassNode srcClass;
+ if (methodSrcOwner.equals(targetClassType)) {
+ srcClass = targetClass;
+ } else {
+ try {
+ srcClass = this.classNodeProvider.classNode(methodSrcOwner);
+ } catch (NoSuchTypeExists e) {
+ methodExceptions.notifyFromException(e);
+ continue;
}
}
- return dasmClassExceptions.notifications();
+ TransformRedirects transformRedirects = new TransformRedirects(transform.redirectSets(), this.mappingsProvider);
+ if (transform.inPlace()) {
+ applyRedirects(srcClass, transform.srcMethod(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true);
+ } else {
+ // FIXME: java 8 synthetic accessor methods
+ cloneAndApplyRedirects(srcClass, targetClass, transform.srcMethod(), transform.dstMethodName(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true);
+ }
}
+
+ return dasmClassExceptions.notifications();
}
/**
@@ -296,19 +294,18 @@ private void cloneAndApplyLambdaRedirects(ClassNode srcClass, ClassNode targetCl
forEachLambdaInvocation(srcClass, method, (classMethod, handle, lambdaNode) -> {
String newName = "dasm$redirect$" + classMethod.method().getName();
lambdaRedirects.put(handle, newName);
- try (NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode)) {
- cloneAndApplyRedirects(
- srcClass,
- targetClass,
- classMethod,
- newName,
- redirects,
- new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET),
- new MethodTransform.OriginalTransformData(srcClass.name, method),
- lambdaExceptions,
- debugLogging
- );
- }
+ NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode);
+ cloneAndApplyRedirects(
+ srcClass,
+ targetClass,
+ classMethod,
+ newName,
+ redirects,
+ new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET),
+ new MethodTransform.OriginalTransformData(srcClass.name, method),
+ lambdaExceptions,
+ debugLogging
+ );
});
Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name));
@@ -327,17 +324,16 @@ private void cloneAndApplyLambdaRedirects(ClassNode srcClass, ClassNode targetCl
private void applyLambdaRedirects(ClassNode srcClass, MethodNode method, TransformRedirects redirects,
NotifyStack methodExceptions, boolean debugLogging) {
forEachLambdaInvocation(srcClass, method, (classMethod, handle, lambdaNode) -> {
- try (NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode)) {
- applyRedirects(
- srcClass,
- classMethod,
- redirects,
- new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET),
- new MethodTransform.OriginalTransformData(srcClass.name, method), // Assume lambas are always private, the passed in method here is therefore irrelevant
- lambdaExceptions,
- debugLogging
- );
- }
+ NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode);
+ applyRedirects(
+ srcClass,
+ classMethod,
+ redirects,
+ new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET),
+ new MethodTransform.OriginalTransformData(srcClass.name, method), // Assume lambas are always private, the passed in method here is therefore irrelevant
+ lambdaExceptions,
+ debugLogging
+ );
});
}
diff --git a/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java b/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java
index 7f9649a..2893d26 100644
--- a/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java
+++ b/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java
@@ -28,7 +28,7 @@ public TypeRemapper(Map typeRedirectsIn, boolean debug
mappingsProvider.remapType(type).getInternalName(), mappingsProvider.remapType(typeRedirectsIn.get(type).type()).getInternalName());
}
- typeRedirects.forEach((old, n) -> LOGGER.info("Type mapping: " + old + " -> " + n));
+ typeRedirects.forEach((old, n) -> LOGGER.trace("Type mapping: " + old + " -> " + n));
}
@@ -40,7 +40,7 @@ public String map(final String key) {
String mapped = typeRedirects.get(key);
if (mapped == null) {
if (this.debugLogging) {
- LOGGER.info("NOTE: handling CLASS redirect to self: " + key);
+ LOGGER.trace("NOTE: handling CLASS redirect to self: " + key);
}
typeRedirects.put(key, key);
return key;
diff --git a/src/main/java/io/github/notstirred/dasm/util/Format.java b/src/main/java/io/github/notstirred/dasm/util/Format.java
index ef0882c..904f724 100644
--- a/src/main/java/io/github/notstirred/dasm/util/Format.java
+++ b/src/main/java/io/github/notstirred/dasm/util/Format.java
@@ -45,4 +45,8 @@ public static String format(MethodNode methodNode) {
public static String format(ClassNode classNode) {
return formatType(Type.getObjectType(classNode.name));
}
+
+ public static String formatObjectType(Type type) {
+ return formatType(type);
+ }
}
diff --git a/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java b/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java
index 010f72a..114a248 100644
--- a/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java
+++ b/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java
@@ -3,6 +3,7 @@
import io.github.notstirred.dasm.exception.DasmException;
import io.github.notstirred.dasm.notify.Notification;
import lombok.Getter;
+import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
@@ -13,11 +14,21 @@
import java.util.stream.Collector;
import static io.github.notstirred.dasm.util.Format.format;
+import static io.github.notstirred.dasm.util.Format.formatObjectType;
-public class NotifyStack implements AutoCloseable {
- private final List stack = new ArrayList<>();
+public class NotifyStack {
+ private final List stack;
@Getter
- private final List notifications = new ArrayList<>();
+ private final List notifications;
+
+ public NotifyStack() {
+ this(new ArrayList<>(), new ArrayList<>());
+ }
+
+ private NotifyStack(List stack, List notifications) {
+ this.stack = stack;
+ this.notifications = notifications;
+ }
public static NotifyStack of(ClassNode classNode) {
NotifyStack notifyStack = new NotifyStack();
@@ -41,23 +52,28 @@ public void notifyFromException(DasmException e) {
}
public NotifyStack push(ClassNode classNode) {
- this.stack.add(format(classNode));
- return this;
+ ArrayList newStack = new ArrayList<>(this.stack);
+ newStack.add(format(classNode));
+ return new NotifyStack(newStack, this.notifications);
}
public NotifyStack push(MethodNode methodNode) {
- this.stack.add(format(methodNode));
- return this;
+ ArrayList newStack = new ArrayList<>(this.stack);
+ newStack.add(format(methodNode));
+ return new NotifyStack(newStack, this.notifications);
}
public NotifyStack push(FieldNode fieldNode) {
- this.stack.add(format(fieldNode));
- return this;
+ ArrayList newStack = new ArrayList<>(this.stack);
+ newStack.add(format(fieldNode));
+ return new NotifyStack(newStack, this.notifications);
}
- public NotifyStack pop() {
- this.stack.remove(this.stack.size() - 1);
- return this;
+ public NotifyStack push(Type type) {
+ assert type.getSort() == Type.OBJECT;
+ ArrayList newStack = new ArrayList<>(this.stack);
+ newStack.add(formatObjectType(type));
+ return new NotifyStack(newStack, this.notifications);
}
public boolean hasError() {
@@ -76,9 +92,4 @@ public NotifyStack join(NotifyStack other) {
public static Collector joining() {
return Collector.of(NotifyStack::new, NotifyStack::join, NotifyStack::join, Collector.Characteristics.IDENTITY_FINISH);
}
-
- @Override
- public void close() {
- this.pop();
- }
}
diff --git a/src/test/java/io/github/notstirred/dasm/test/TestHarness.java b/src/test/java/io/github/notstirred/dasm/test/TestHarness.java
index 487c730..342767c 100644
--- a/src/test/java/io/github/notstirred/dasm/test/TestHarness.java
+++ b/src/test/java/io/github/notstirred/dasm/test/TestHarness.java
@@ -11,7 +11,6 @@
import io.github.notstirred.dasm.test.targets.inherited_transforms.Foo;
import io.github.notstirred.dasm.test.utils.ByteArrayClassLoader;
import io.github.notstirred.dasm.transformer.Transformer;
-import io.github.notstirred.dasm.transformer.data.ClassTransform;
import io.github.notstirred.dasm.transformer.data.MethodTransform;
import io.github.notstirred.dasm.util.CachingClassProvider;
import io.github.notstirred.dasm.util.Pair;
@@ -38,55 +37,42 @@
import static org.objectweb.asm.Opcodes.ASM9;
public class TestHarness {
+ private static final Path DASM_OUT = Path.of(".dasm.out");
+
/**
* Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied
*/
public static void verifyMethodTransformsValid(Class> actualClass, Class> expectedClass, Class> dasmClass) {
- ClassNode actual = classNodeFromClass(actualClass);
- ClassNode expected = classNodeFromClass(expectedClass);
- ClassNode dasm = classNodeFromClass(dasmClass);
-
- CachingClassProvider classProvider = new CachingClassProvider(TestHarness::getBytesForClassName);
- Transformer transformer = new Transformer(classProvider, MappingsProvider.IDENTITY);
-
- AnnotationParser annotationParser = new AnnotationParser(classProvider);
-
- try {
- Collection methodTransforms = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual)).first()
- .buildMethodTargets(dasm, "").first().get();
-
- transformer.transform(actual, methodTransforms);
- } catch (DasmException e) {
- throw new Error("", e);
- }
-
- // Write and re-read class bytes to fix issues with label nodes being wonky
- byte[] bytecode = classNodeToBytes(actual);
- ClassNode reparsedClassNode = classNodeFromBytes(bytecode);
+ verifyTransformValid(actualClass, expectedClass, dasmClass, DASM_OUT.resolve("method_transforms"),
+ (context, classNode) -> context.buildMethodTargets(classNode, ""),
+ Transformer::transform
+ );
+ }
- try {
- Path path = Path.of(".dasm.out/method_transforms/" + reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath();
- Path actualPath = path.getParent().resolve("ACTUAL").resolve(path.getFileName());
- createDirectoriesIfNotExists(actualPath.getParent());
- Files.write(actualPath, bytecode);
- Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName() + ".class");
- createDirectoriesIfNotExists(expectedPath.getParent());
- Files.write(expectedPath, classNodeToBytes(expected));
- } catch (IOException ex) {
- throw new RuntimeException(ex);
- }
+ /**
+ * Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied
+ */
+ public static void verifyClassTransformValid(Class> actualClass, Class> expectedClass, Class> dasmClass) {
+ verifyTransformValid(actualClass, expectedClass, dasmClass, DASM_OUT.resolve("class_transforms"),
+ DasmContext::buildClassTarget,
+ Transformer::transform
+ );
+ }
- expected.methods.sort(Comparator.comparing(methodNode -> methodNode.name));
- reparsedClassNode.methods.sort(Comparator.comparing(methodNode -> methodNode.name));
+ @FunctionalInterface
+ interface BuildTargets {
+ Pair, List> buildTargets(DasmContext context, ClassNode classNode) throws DasmException;
+ }
- assertClassNodesEqual(reparsedClassNode, expected);
- callAllMethodsWithDummies(actualClass, expectedClass, reparsedClassNode);
+ @FunctionalInterface
+ interface DoTransform {
+ void doTransform(Transformer transformer, ClassNode classNode, T transforms) throws DasmException;
}
/**
* Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied
*/
- public static void verifyClassTransformValid(Class> actualClass, Class> expectedClass, Class> dasmClass) {
+ private static void verifyTransformValid(Class> actualClass, Class> expectedClass, Class> dasmClass, Path basePath, BuildTargets buildTargets, DoTransform doTransform) {
ClassNode actual = classNodeFromClass(actualClass);
ClassNode expected = classNodeFromClass(expectedClass);
ClassNode dasm = classNodeFromClass(dasmClass);
@@ -97,13 +83,33 @@ public static void verifyClassTransformValid(Class> actualClass, Class> expe
AnnotationParser annotationParser = new AnnotationParser(classProvider);
try {
- DasmContext context = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual)).first();
+ Pair> dasmContextListPair = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual));
+ DasmContext context;
+ if (dasmContextListPair.first() != null) {
+ context = dasmContextListPair.first();
+ } else {
+ DasmException dasmException = new DasmException("");
+ dasmContextListPair.second().forEach(notification -> {
+ dasmException.addSuppressed(new Exception(notification.message));
+ });
+ throw dasmException;
+ }
+
dasm.name = actual.name;
- ClassTransform methodTransforms = context.buildClassTarget(dasm).first().get();
+ Pair, List> targetsAndNotifications = buildTargets.buildTargets(context, dasm);
+ if (targetsAndNotifications.first().isPresent()) {
+ T transforms = targetsAndNotifications.first().get();
- transformer.transform(actual, methodTransforms);
+ doTransform.doTransform(transformer, actual, transforms);
+ } else {
+ DasmException dasmException = new DasmException("");
+ targetsAndNotifications.second().forEach(notification -> {
+ dasmException.addSuppressed(new Exception(notification.message));
+ });
+ throw dasmException;
+ }
} catch (DasmException e) {
- throw new Error("foo", e);
+ throw new Error("Dasm Exception in testing", e);
}
// Write and re-read class bytes to fix issues with label nodes being wonky
@@ -111,17 +117,20 @@ public static void verifyClassTransformValid(Class> actualClass, Class> expe
ClassNode reparsedClassNode = classNodeFromBytes(bytecode);
try {
- Path path = Path.of(".dasm.out/class_transforms/" + reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath();
+ Path path = basePath.resolve(reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath();
Path actualPath = path.getParent().resolve("ACTUAL").resolve(path.getFileName());
createDirectoriesIfNotExists(actualPath.getParent());
Files.write(actualPath, bytecode);
- Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName());
+ Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName() + ".class");
createDirectoriesIfNotExists(expectedPath.getParent());
Files.write(expectedPath, classNodeToBytes(expected));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
+ expected.methods.sort(Comparator.comparing(methodNode -> methodNode.name));
+ reparsedClassNode.methods.sort(Comparator.comparing(methodNode -> methodNode.name));
+
assertClassNodesEqual(reparsedClassNode, expected);
callAllMethodsWithDummies(actualClass, expectedClass, reparsedClassNode);
}
diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java
new file mode 100644
index 0000000..a9516d8
--- /dev/null
+++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java
@@ -0,0 +1,11 @@
+package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets;
+
+import io.github.notstirred.dasm.test.targets.CubePos;
+
+public class AddFieldToMethodToSetsInput {
+ void method1() {
+ System.out.println(CubePos.MASK);
+ CubePos.MASK = 34;
+ System.out.println(CubePos.MASK);
+ }
+}
diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java
new file mode 100644
index 0000000..1cff80a
--- /dev/null
+++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java
@@ -0,0 +1,17 @@
+package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets;
+
+import io.github.notstirred.dasm.test.targets.CubePos;
+
+public class AddFieldToMethodToSetsOutput {
+ void method1() {
+ System.out.println(CubePos.MASK);
+ CubePos.MASK = 34;
+ System.out.println(CubePos.MASK);
+ }
+
+ void method1out() {
+ System.out.println(TestAddFieldToMethodToSets.getMask());
+ TestAddFieldToMethodToSets.setMask(34);
+ System.out.println(TestAddFieldToMethodToSets.getMask());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java
new file mode 100644
index 0000000..94c405c
--- /dev/null
+++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java
@@ -0,0 +1,46 @@
+package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets;
+
+import io.github.notstirred.dasm.api.annotations.Dasm;
+import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToMethodToSets;
+import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets;
+import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer;
+import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet;
+import io.github.notstirred.dasm.api.annotations.selector.FieldSig;
+import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
+import io.github.notstirred.dasm.api.annotations.selector.Ref;
+import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod;
+import io.github.notstirred.dasm.test.targets.CubePos;
+import io.github.notstirred.dasm.test.tests.integration.BaseMethodTest;
+
+import static io.github.notstirred.dasm.test.tests.integration.TestData.single;
+
+/**
+ * A trivial test for a static {@link AddMethodToSets}
+ */
+@Dasm(value = TestAddFieldToMethodToSets.Set.class, target = @Ref(AddFieldToMethodToSetsInput.class))
+public class TestAddFieldToMethodToSets extends BaseMethodTest {
+ public TestAddFieldToMethodToSets() {
+ super(single(AddFieldToMethodToSetsInput.class, AddFieldToMethodToSetsOutput.class, TestAddFieldToMethodToSets.class));
+ }
+
+ @TransformFromMethod(value = @MethodSig("method1()V"))
+ native String method1out();
+
+ @RedirectSet
+ public interface Set {
+ @InterOwnerContainer(from = @Ref(CubePos.class), to = @Ref(TestAddFieldToMethodToSets.class))
+ abstract class CubePos_to_TestAddToSets_redirects {
+ }
+ }
+
+ static int mask = 123;
+
+ @AddFieldToMethodToSets(containers = Set.CubePos_to_TestAddToSets_redirects.class, setter = "setMask", field = @FieldSig(type = @Ref(int.class), name = "MASK"))
+ public static int getMask() {
+ return mask;
+ }
+
+ public static void setMask(int i) {
+ mask = i;
+ }
+}
\ No newline at end of file