Skip to content

Commit

Permalink
Merge pull request #236 from geoand/default
Browse files Browse the repository at this point in the history
Traverse class hierarchy to find matching properties
  • Loading branch information
cowtowncoder committed Feb 13, 2024
2 parents 5536d0f + c9275d5 commit 98b4017
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ public StackManipulation supply(T prop) {
private static class SingleMethodStackManipulation<T extends OptimizedSettableBeanProperty<T>>
extends AbstractSinglePropStackManipulation<T> {

private static final TypeDescription OBJECT_TYPE_DESCRIPTION = TypeDescription.ForLoadedType.of(Object.class);

SingleMethodStackManipulation(TypeDescription beanClassDescription,
T prop,
MethodVariableAccess beanValueAccess) {
Expand All @@ -522,15 +524,66 @@ protected StackManipulation invocationOperation(AnnotatedMember annotatedMember,
if (matchingMethods.size() == 1) { //method was declared on class
return MethodInvocation.invoke(matchingMethods.getOnly());
}
if (matchingMethods.isEmpty()) { //method was not found on class, try super class
return invocationOperation(annotatedMember, beanClassDescription.getSuperClass());
if (matchingMethods.isEmpty()) { //method was not found on class, try interfaces and superclass

TypeDefinition klass = beanClassDescription;
Deque<TypeDefinition> toProcess = new LinkedList<>();
do {
toProcess.addAll(klass.getInterfaces()); //add the interfaces of the current class we are at, so we can process them later
if (OBJECT_TYPE_DESCRIPTION.equals(klass) || (klass.getSuperClass() == null)) {
Set<TypeDefinition> seen = new HashSet<>(toProcess);
while (!toProcess.isEmpty()) { // process all the interfaces (including super interfaces which will be looked up on demand)
TypeDefinition iface = toProcess.poll();

MethodList<MethodDescription> matches = getMatchingMethods(iface, methodName);
if (matches.size() == 1) {
return MethodInvocation.invoke(matches.getOnly());
} else if (matches.size() > 1) { // should never happen
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}

// now add the super interfaces
for (TypeDefinition i : iface.getInterfaces()) {
if (!seen.contains(i)) {
seen.add(i);
toProcess.add(i);
}
}
}

// we exhausted super classes and interfaces and came up empty...
throw new IllegalStateException("Could not find definition of method: " + methodName);
}

MethodList<MethodDescription> matches = getMatchingMethods(klass, methodName);
if (matches.size() == 1) {
return MethodInvocation.invoke(matches.getOnly());
} else if (matches.size() > 1) { // should never happen
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}

// no match, keep going
if (klass.isInterface()) {
klass = OBJECT_TYPE_DESCRIPTION;
} else {
klass = klass.getSuperClass();
}

} while (klass != null);

// we exhausted super classes and interfaces and came up empty...
throw new IllegalStateException("Could not find definition of method: " + methodName);
}
else { //should never happen
throw new IllegalStateException("Could not find definition of method: " + methodName);
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}

}

private MethodList<MethodDescription> getMatchingMethods(TypeDefinition beanClassDescription, String methodName) {
return (MethodList<MethodDescription>) beanClassDescription.getDeclaredMethods().filter(named(methodName));
}

}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tools.jackson.module.afterburner.ser;

import net.bytebuddy.description.type.TypeList;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.ser.BeanPropertyWriter;
import tools.jackson.module.afterburner.deser.PropertyMutatorCollector;
Expand Down Expand Up @@ -266,9 +267,11 @@ private StackManipulation loadLocalVar() {
private static class SingleMethodStackManipulation<T extends OptimizedBeanPropertyWriter<T>>
extends AbstractSinglePropStackManipulation<T> {

private static final TypeDescription OBJECT_TYPE_DESCRIPTION = TypeDescription.ForLoadedType.of(Object.class);

SingleMethodStackManipulation(TypeDescription beanClassDescription,
T prop,
MethodReturn methodReturn) {
T prop,
MethodReturn methodReturn) {
super(beanClassDescription, prop, methodReturn);
}

Expand All @@ -277,20 +280,69 @@ protected StackManipulation invocationOperation(
AnnotatedMember annotatedMember, TypeDefinition beanClassDescription) {

final String methodName = annotatedMember.getName();
@SuppressWarnings("unchecked")
final MethodList<MethodDescription> matchingMethods =
(MethodList<MethodDescription>) beanClassDescription.getDeclaredMethods().filter(named(methodName));


final MethodList<MethodDescription> matchingMethods = getMatchingMethods(beanClassDescription, methodName);
if (matchingMethods.size() == 1) { //method was declared on class
return MethodInvocation.invoke(matchingMethods.getOnly());
}
if (matchingMethods.isEmpty()) { //method was not found on class, try super class
return invocationOperation(annotatedMember, beanClassDescription.getSuperClass());
if (matchingMethods.isEmpty()) { //method was not found on class, try interfaces and superclass

TypeDefinition klass = beanClassDescription;
Deque<TypeDefinition> toProcess = new LinkedList<>();
do {
toProcess.addAll(klass.getInterfaces()); //add the interfaces of the current class we are at, so we can process them later
if (OBJECT_TYPE_DESCRIPTION.equals(klass) || (klass.getSuperClass() == null)) {
Set<TypeDefinition> seen = new HashSet<>(toProcess);
while (!toProcess.isEmpty()) { // process all the interfaces (including super interfaces which will be looked up on demand)
TypeDefinition iface = toProcess.poll();

MethodList<MethodDescription> matches = getMatchingMethods(iface, methodName);
if (matches.size() == 1) {
return MethodInvocation.invoke(matches.getOnly());
} else if (matches.size() > 1) { // should never happen
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}

// now add the super interfaces
for (TypeDefinition i : iface.getInterfaces()) {
if (!seen.contains(i)) {
seen.add(i);
toProcess.add(i);
}
}
}

// we exhausted super classes and interfaces and came up empty...
throw new IllegalStateException("Could not find definition of method: " + methodName);
}

MethodList<MethodDescription> matches = getMatchingMethods(klass, methodName);
if (matches.size() == 1) {
return MethodInvocation.invoke(matches.getOnly());
} else if (matches.size() > 1) { // should never happen
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}

// no match, keep going
if (klass.isInterface()) {
klass = OBJECT_TYPE_DESCRIPTION;
} else {
klass = klass.getSuperClass();
}

} while (klass != null);

// we exhausted super classes and interfaces and came up empty...
throw new IllegalStateException("Could not find definition of method: " + methodName);
}
else { //should never happen
throw new IllegalStateException("Could not find definition of method: " + methodName);
throw new IllegalStateException("Multiple definitions of method " + methodName + " found");
}
}

private MethodList<MethodDescription> getMatchingMethods(TypeDefinition beanClassDescription, String methodName) {
return (MethodList<MethodDescription>) beanClassDescription.getDeclaredMethods().filter(named(methodName));
}
}

private static class SingleMethodStackManipulationSupplier<T extends OptimizedBeanPropertyWriter<T>> implements
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tools.jackson.module.afterburner.failing;
package tools.jackson.module.afterburner.deser.java8;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tools.jackson.module.afterburner.failing;
package tools.jackson.module.afterburner.deser.java8;

import tools.jackson.databind.ObjectMapper;
import tools.jackson.module.afterburner.AfterburnerTestBase;
Expand Down
4 changes: 3 additions & 1 deletion release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ Other contributors:

Georgios Andrianakis (geoand@github)

* Contributed [MrBean#28]: Replace ASM code-gen with ByteBuddy
* Contributed #28: (mrbean) Replace ASM code-gen with ByteBuddy
(3.0.0)
* Contributed #225: Afterburner 3.0 fails to handle interface "default" methods
(3.0.0)
3 changes: 3 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ Modules:
(contributed by Georgios A))
#40: Remove `jackson-module-paranamer` from Jackson 3.0
#182: Rename "com.fasterxml.jackson" -> "tools.jackson"
#225: Afterburner 3.0 fails to handle interface "default" methods
(contributed by Georgios A))

0 comments on commit 98b4017

Please sign in to comment.