Skip to content

Commit

Permalink
Support invocation, overriding and intercepting of default methods.
Browse files Browse the repository at this point in the history
Fixed #25
  • Loading branch information
kenwenzel committed Jul 1, 2022
1 parent baa84a3 commit f23e42f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 25 deletions.
Expand Up @@ -382,8 +382,7 @@ private void mergeProperty(PropertyDescriptor pd,
gen.mark(end);
}
}

private void overrideMergeMethod(BehaviourClassNode node) throws Exception {
private void overrideMergeMethod(BehaviourClassNode node, Collection<PropertyDescriptor> properties) throws Exception {
Method merge = Mergeable.class.getMethod("merge", Object.class);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(
node.addExtendedMethod(merge, definer));
Expand All @@ -397,8 +396,7 @@ private void overrideMergeMethod(BehaviourClassNode node) throws Exception {
gen.ifZCmp(IFEQ, notInstanceOf);

// access property value with corresponding interface method
for (PropertyDescriptor pd : propertyMapper.getProperties(node
.getParentClass())) {
for (PropertyDescriptor pd : properties) {
gen.loadArg(0);
gen.checkCast(Type.getType(pd.getReadMethod().getDeclaringClass()));
gen.invoke(pd.getReadMethod());
Expand All @@ -417,8 +415,7 @@ private void overrideMergeMethod(BehaviourClassNode node) throws Exception {
// access property set with "getPropertySet" method
Method getPropertySet = PropertySetOwner.class.getMethod(
"getPropertySet", String.class);
for (PropertyDescriptor pd : propertyMapper.getProperties(node
.getParentClass())) {
for (PropertyDescriptor pd : properties) {
// load other property set
gen.loadArg(0);
gen.push(pd.getPredicate());
Expand Down Expand Up @@ -552,7 +549,7 @@ public void process(BehaviourClassNode classNode) throws Exception {
implementProperty(pd, classNode);
}

overrideMergeMethod(classNode);
overrideMergeMethod(classNode, properties);
overrideRefreshMethod(classNode);
overrideGetPropertySetMethod(classNode, properties);
}
Expand Down
Expand Up @@ -22,20 +22,30 @@ public AbstractPropertyMapper() {

@Override
public Collection<PropertyDescriptor> getProperties(Class<?> concept) {
Set<String> seenPropertyNames = new HashSet<>();
List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();
Set<String> seenPropertyNames = null;
List<PropertyDescriptor> properties = new ArrayList<>();
while (concept != null) {
for (Method method : concept.getDeclaredMethods()) {
if (! method.isDefault() && isMappedGetter(method)) {
PropertyDescriptor pd = createPropertyDescriptor(method);
// ensure that overwritten (polymorphic) properties are filtered to avoid duplicate fields
if (seenPropertyNames.add(pd.getName())) {
if (seenPropertyNames == null || seenPropertyNames.add(pd.getName())) {
properties.add(pd);
}
}
}
// this is required if concept is a behaviour class
concept = concept.getSuperclass();
if (Object.class.equals(concept)) {
concept = null;
}
if (seenPropertyNames == null && concept != null) {
// initialize seen properties if class hierarchy is walked
seenPropertyNames = new HashSet<>();
for (PropertyDescriptor pd : properties) {
seenPropertyNames.add(pd.getName());
}
}
}
return properties;
}
Expand Down
Expand Up @@ -140,14 +140,14 @@ public Class<?> compose() throws Exception {
if (baseClass != null && !Object.class.equals(baseClass)) {
javaClasses.add(baseClass);
}

methods = getMethods();

namedMethods = new HashMap<String, Method>(methods.size());
for (Method method : methods) {
if (method.isAnnotationPresent(Iri.class)) {
String uri = method.getAnnotation(Iri.class).value();
if (!namedMethods.containsKey(uri)
|| !isBridge(method, methods)) {
if (!namedMethods.containsKey(uri) || !isBridge(method, methods)) {
namedMethods.put(uri, method);
}
}
Expand Down Expand Up @@ -268,8 +268,7 @@ private Type[] toTypes(Class<?>[] classes) {
return types;
}

private boolean implementMethod(Method method, String name, boolean bridge)
throws Exception {
private boolean implementMethod(Method method, String name, boolean bridge) throws Exception {
List<Class<?>> chain = chain(method);
List<Object[]> implementations = getImplementations(chain, method);
if (implementations.isEmpty()) {
Expand All @@ -281,8 +280,7 @@ private boolean implementMethod(Method method, String name, boolean bridge)
for (Object[] ar : implementations) {
Method m = (Method) ar[1];
Class<?>[] parameterTypes = m.getParameterTypes();
if (parameterTypes.length == 1
&& MethodInvocation.class.equals(parameterTypes[0])) {
if (parameterTypes.length == 1 && MethodInvocation.class.equals(parameterTypes[0])) {
dynamicChained = true;
break;
}
Expand Down Expand Up @@ -469,8 +467,7 @@ private List<Class<?>> chain(Method method) throws Exception {
/**
* @return list of <String, Method>
*/
private List<Object[]> getImplementations(List<Class<?>> behaviours,
Method method) throws Exception {
private List<Object[]> getImplementations(List<Class<?>> behaviours, Method method) throws Exception {
List<Object[]> list = new ArrayList<Object[]>();
Class<?> type = method.getReturnType();
Class<?> superclass = compositeClass.getParentClass();
Expand Down Expand Up @@ -579,12 +576,20 @@ private void callMethod(Object target, Method method,
gen.invokeSpecial(Type.getType(baseClass),
org.objectweb.asm.commons.Method.getMethod(method));
} else {
if (!"this".equals(target)) {
boolean targetIsThis = "this".equals(target);
if (! targetIsThis) {
loadBehaviour((Class<?>) target, gen);
}
gen.loadArgs();
gen.invokeVirtual(Type.getType(method.getDeclaringClass()),
org.objectweb.asm.commons.Method.getMethod(method));
if (method.isDefault()) {
// call the default method
gen.invokeVirtual(targetIsThis ? compositeClass.getType() : Type.getType((Class<?>) target),
org.objectweb.asm.commons.Method.getMethod(method));
} else {
// call an interface method
gen.invokeVirtual(Type.getType(method.getDeclaringClass()),
org.objectweb.asm.commons.Method.getMethod(method));
}
}
}

Expand All @@ -593,13 +598,11 @@ private boolean isMethodPresent(Class<?> javaClass, Method method)
return getMethod(javaClass, method) != null;
}

private Method getMethod(Class<?> javaClass, Method method)
throws Exception {
private Method getMethod(Class<?> javaClass, Method method) throws Exception {
Class<?>[] types = method.getParameterTypes();
try {
Method m = javaClass.getMethod(method.getName(), types);
if (!isAbstract(m.getModifiers()) && !isTransient(m.getModifiers())
&& !isObjectMethod(m)) {
if (!m.isDefault() && !isAbstract(m.getModifiers()) && !isTransient(m.getModifiers()) && !isObjectMethod(m)) {
return m;
}
} catch (NoSuchMethodException e) {
Expand Down
@@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2022 Fraunhofer IWU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.composition.test;

import net.enilink.composition.annotations.Iri;
import net.enilink.composition.annotations.ParameterTypes;
import net.enilink.composition.mappers.RoleMapper;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Assert;
import org.junit.Test;

/**
* Tests for composition with default interface methods.
*/
public class DefaultMethodTest extends CompositionTestCase {
@Iri("urn:test:Value")
public interface Value {
}

@Iri("urn:test:ConcreteType")
public interface Concept {
Value getValue();

void setValue(Value value);

default boolean hasValue() {
return getValue() != null;
}

default int testAbstractMethod() {
return 1;
}

default int testInterceptMethod() {
return 0;
}

default String testOnlyDefaultNoImpl() {
return "a";
}
}

public static abstract class ConceptBehaviour implements Concept {
Value value;

@Override
public Value getValue() {
return value;
}

@Override
public void setValue(Value value) {
this.value = value;
}
}

public static abstract class ConceptBehaviour2 implements Concept {
@Override
public boolean hasValue() {
return Concept.super.hasValue();
}

@Override
public int testInterceptMethod() {
return 1;
}
}

public static abstract class ConceptBehaviour3 implements Concept {
@Override
public abstract int testAbstractMethod();
}

public static abstract class ConceptBehaviour4 implements Concept {
@Override
public abstract int testAbstractMethod();

@ParameterTypes({})
public int testInterceptMethod(MethodInvocation invocation) {
return 3;
}
}

@Override
protected void initRoleMapper(RoleMapper<String> roleMapper) {
super.initRoleMapper(roleMapper);

roleMapper.addConcept(Value.class);
roleMapper.addConcept(Concept.class);
roleMapper.addBehaviour(ConceptBehaviour.class);
roleMapper.addBehaviour(ConceptBehaviour2.class);
roleMapper.addBehaviour(ConceptBehaviour3.class);
roleMapper.addBehaviour(ConceptBehaviour4.class);
}

@Test
public void testCreateAndSet() throws Exception {
Concept object = objectFactory.createObject(Concept.class);
Assert.assertEquals(object.getValue(), null);
Assert.assertFalse(object.hasValue());
Value value = objectFactory.createObject(Value.class);
object.setValue(value);
Assert.assertEquals(object.getValue(), value);
Assert.assertTrue(object.hasValue());
Assert.assertEquals(1, object.testAbstractMethod());
Assert.assertEquals(3, object.testInterceptMethod());
Assert.assertEquals("a", object.testOnlyDefaultNoImpl());
}
}

0 comments on commit f23e42f

Please sign in to comment.