diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java
index e3acac8cc..6c6f57fa5 100644
--- a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java
+++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java
@@ -13,6 +13,7 @@
package org.eclipse.syson.util;
import java.io.Serializable;
+import java.lang.invoke.MethodType;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -72,6 +73,10 @@
* }
*
*
+ * Overloaded services: if several service methods share the same name, use the factory overloads that also take the
+ * service class and Java parameter types, for example
+ * {@code ServiceMethod.of1(EObjectServices.class, EObjectServices::eGet, EObject.class, EStructuralFeature.class)}.
+ *
* Performance: this uses reflection once per reference at startup to read a method name. The cost is negligible
* compared to normal init work.
*
@@ -80,12 +85,15 @@
*/
public final class ServiceMethod {
+ private final Method declaration;
+
private final String name;
private final int arity;
- private ServiceMethod(String name, int arity) {
- this.name = name;
+ private ServiceMethod(Method declaration, int arity) {
+ this.declaration = declaration;
+ this.name = declaration.getName();
this.arity = arity;
}
@@ -98,6 +106,15 @@ public String name() {
return this.name;
}
+ /**
+ * the Java declaration that will be called from AQL.
+ *
+ * @return the declaration.
+ */
+ public Method declaration() {
+ return this.declaration;
+ }
+
/**
* Build {@code aql:self.method(...)} for the captured service name.
*
@@ -149,50 +166,173 @@ public String aql(String var, String... params) {
* Instance method with signature {@code R method(T self)}.
*/
public static ServiceMethod of0(Inst0 ref) {
- return new ServiceMethod(methodName(ref), 0);
+ return new ServiceMethod(method(ref), 0);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self)}.
+ *
+ * Use this overload when the referenced Java service is overloaded and you need to disambiguate on the
+ * {@code self} type.
+ */
+ public static ServiceMethod of0(Class selfType, Inst0 ref) {
+ return new ServiceMethod(method(ref, selfType), 0);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self)}.
+ *
+ * Use this overload when the referenced Java service is overloaded and you need to disambiguate on the declaring
+ * service and {@code self} types.
+ */
+ public static ServiceMethod of0(Class serviceType, Inst0 ref, Class selfType) {
+ return new ServiceMethod(method(serviceType, ref, selfType), 0);
}
/**
* Instance method with signature {@code R method(T self, P1 p1)}.
*/
public static ServiceMethod of1(Inst1 ref) {
- return new ServiceMethod(methodName(ref), 1);
+ return new ServiceMethod(method(ref), 1);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1)}.
+ *
+ * Use this overload when the referenced Java service is overloaded and you need to disambiguate on parameter
+ * types.
+ */
+ public static ServiceMethod of1(Class selfType, Class p1Type, Inst1 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type), 1);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1)}.
+ *
+ * Use this overload when the referenced Java service is overloaded and you need to disambiguate on the declaring
+ * service and parameter types.
+ */
+ public static ServiceMethod of1(Class serviceType, Inst1 ref, Class selfType, Class p1Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type), 1);
}
/**
* Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
*/
public static ServiceMethod of2(Inst2 ref) {
- return new ServiceMethod(methodName(ref), 2);
+ return new ServiceMethod(method(ref), 2);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
+ */
+ public static ServiceMethod of2(Class selfType, Class p1Type, Class p2Type, Inst2 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type), 2);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
+ */
+ public static ServiceMethod of2(Class serviceType, Inst2 ref, Class selfType, Class p1Type, Class p2Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type), 2);
}
/**
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
*/
public static ServiceMethod of3(Inst3 ref) {
- return new ServiceMethod(methodName(ref), 3);
+ return new ServiceMethod(method(ref), 3);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
+ */
+ public static ServiceMethod of3(Class selfType, Class p1Type, Class p2Type, Class p3Type, Inst3 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type), 3);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
+ */
+ public static ServiceMethod of3(Class serviceType, Inst3 ref, Class selfType, Class p1Type, Class p2Type,
+ Class p3Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type), 3);
}
/**
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
*/
public static ServiceMethod of4(Inst4 ref) {
- return new ServiceMethod(methodName(ref), 4);
+ return new ServiceMethod(method(ref), 4);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
+ */
+ public static ServiceMethod of4(Class selfType, Class p1Type, Class p2Type, Class p3Type, Class p4Type,
+ Inst4 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type), 4);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
+ */
+ public static ServiceMethod of4(Class serviceType, Inst4 ref, Class selfType, Class p1Type,
+ Class p2Type, Class p3Type, Class p4Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type), 4);
}
/**
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
*/
public static ServiceMethod of5(Inst5 ref) {
- return new ServiceMethod(methodName(ref), 5);
+ return new ServiceMethod(method(ref), 5);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
+ */
+ public static ServiceMethod of5(Class selfType, Class p1Type, Class p2Type, Class p3Type, Class p4Type,
+ Class p5Type, Inst5 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type), 5);
}
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
+ */
+ // CHECKSTYLE:OFF
+ public static ServiceMethod of5(Class serviceType, Inst5 ref, Class selfType, Class p1Type,
+ Class p2Type, Class p3Type, Class p4Type, Class p5Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type), 5);
+ }
+ // CHECKSTYLE:ON
+
/**
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
*/
public static ServiceMethod of6(Inst6 ref) {
- return new ServiceMethod(methodName(ref), 6);
+ return new ServiceMethod(method(ref), 6);
+ }
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
+ */
+ // CHECKSTYLE:OFF
+ public static ServiceMethod of6(Class selfType, Class p1Type, Class p2Type, Class p3Type, Class p4Type,
+ Class p5Type, Class p6Type, Inst6 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type, p6Type), 6);
}
+ // CHECKSTYLE:ON
+
+ /**
+ * Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
+ */
+ // CHECKSTYLE:OFF
+ public static ServiceMethod of6(Class serviceType, Inst6 ref, Class selfType,
+ Class p1Type, Class p2Type, Class p3Type, Class p4Type, Class p5Type, Class p6Type) {
+ return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type, p6Type), 6);
+ }
+ // CHECKSTYLE:ON
// ---------------------- Factories for static methods ----------------------
@@ -200,28 +340,56 @@ public static ServiceMethod of6(Inst6 ServiceMethod ofStatic0(IStat0 ref) {
- return new ServiceMethod(methodName(ref), 0);
+ return new ServiceMethod(method(ref), 0);
+ }
+
+ /**
+ * Static method with signature {@code R method(T self)}.
+ */
+ public static ServiceMethod ofStatic0(Class selfType, IStat0 ref) {
+ return new ServiceMethod(method(ref, selfType), 0);
}
/**
* Static method with signature {@code R method(T self, P1 p1)}.
*/
public static ServiceMethod ofStatic1(IStat1 ref) {
- return new ServiceMethod(methodName(ref), 1);
+ return new ServiceMethod(method(ref), 1);
+ }
+
+ /**
+ * Static method with signature {@code R method(T self, P1 p1)}.
+ */
+ public static ServiceMethod ofStatic1(Class selfType, Class p1Type, IStat1 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type), 1);
}
/**
* Static method with signature {@code R method(T self, P1 p1, P2 p2)}.
*/
public static ServiceMethod ofStatic2(IStat2 ref) {
- return new ServiceMethod(methodName(ref), 2);
+ return new ServiceMethod(method(ref), 2);
+ }
+
+ /**
+ * Static method with signature {@code R method(T self, P1 p1, P2 p2)}.
+ */
+ public static ServiceMethod ofStatic2(Class selfType, Class p1Type, Class p2Type, IStat2 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type), 2);
}
/**
* Static method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
*/
public static ServiceMethod ofStatic3(IStat3 ref) {
- return new ServiceMethod(methodName(ref), 3);
+ return new ServiceMethod(method(ref), 3);
+ }
+
+ /**
+ * Static method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
+ */
+ public static ServiceMethod ofStatic3(Class selfType, Class p1Type, Class p2Type, Class p3Type, IStat3 ref) {
+ return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type), 3);
}
// ---------------------- SAMs for method references ----------------------
@@ -427,14 +595,42 @@ public interface IStat3 extends Serializable {
// ---------------------- Lambda -> method name ----------------------
- private static String methodName(Serializable lambdaRef) {
+ private static Method method(Serializable lambdaRef, Class>... expectedParameterTypes) {
+ try {
+ SerializedLambda lambda = serializedLambda(lambdaRef);
+ Class> implementationClass = Class.forName(lambda.getImplClass().replace('/', '.'), false, lambdaRef.getClass().getClassLoader());
+ MethodType methodType = MethodType.fromMethodDescriptorString(lambda.getImplMethodSignature(), implementationClass.getClassLoader());
+ Method method = thisClassMethod(implementationClass, lambda.getImplMethodName(), methodType.parameterArray());
+ if (expectedParameterTypes.length > 0 && !Arrays.equals(method.getParameterTypes(), expectedParameterTypes)) {
+ throw new IllegalArgumentException(
+ MessageFormat.format("Resolved method {0} has parameters {1} but expected {2}", method, Arrays.toString(method.getParameterTypes()), Arrays.toString(expectedParameterTypes)));
+ }
+ return method;
+ } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
+ throw new IllegalStateException("Cannot resolve method declaration from lambda", e);
+ }
+ }
+
+ private static Method method(Class> expectedServiceType, Serializable lambdaRef, Class>... expectedParameterTypes) {
+ Method method = method(lambdaRef, expectedParameterTypes);
+ if (!expectedServiceType.isAssignableFrom(method.getDeclaringClass())) {
+ throw new IllegalArgumentException(MessageFormat.format("Resolved method {0} is declared on {1} but expected a service assignable to {2}", method,
+ method.getDeclaringClass().getName(), expectedServiceType.getName()));
+ }
+ return method;
+ }
+
+ private static SerializedLambda serializedLambda(Serializable lambdaRef) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Method writeReplace = lambdaRef.getClass().getDeclaredMethod("writeReplace");
+ writeReplace.setAccessible(true);
+ return (SerializedLambda) writeReplace.invoke(lambdaRef);
+ }
+
+ private static Method thisClassMethod(Class> implementationClass, String methodName, Class>[] parameterTypes) throws NoSuchMethodException {
try {
- Method m = lambdaRef.getClass().getDeclaredMethod("writeReplace");
- m.setAccessible(true);
- SerializedLambda sl = (SerializedLambda) m.invoke(lambdaRef);
- return sl.getImplMethodName();
- } catch (InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
- throw new IllegalStateException("Cannot resolve method name from lambda", e);
+ return implementationClass.getDeclaredMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException exception) {
+ return implementationClass.getMethod(methodName, parameterTypes);
}
}
@@ -457,4 +653,3 @@ private void checkArity(String... params) {
}
}
}
-
diff --git a/backend/services/syson-services/src/test/java/org/eclipse/syson/util/ServiceMethodTest.java b/backend/services/syson-services/src/test/java/org/eclipse/syson/util/ServiceMethodTest.java
index c6ad72f78..64e384b24 100644
--- a/backend/services/syson-services/src/test/java/org/eclipse/syson/util/ServiceMethodTest.java
+++ b/backend/services/syson-services/src/test/java/org/eclipse/syson/util/ServiceMethodTest.java
@@ -15,6 +15,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import java.lang.reflect.Method;
+
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -25,23 +27,261 @@
*/
public class ServiceMethodTest {
+ private static final String AQL_VALUE = "'value'";
+
+ private static final String AQL_INT = "2";
+
@Test
@DisplayName("GIVEN a service method with arity 0, WHEN an AQL expression is constructed with 0 parameter, THEN the expression is returned")
public void givenServiceMethodWithArity0WhenAQLExpressionIsConstructedWith0ParameterThenExpressionIsReturned() {
- String expression = ServiceMethod.of0(ServiceMethodTest::serviceWithArity0).aqlSelf();
- assertThat(expression).isEqualTo("aql:self.serviceWithArity0()");
- expression = ServiceMethod.of0(ServiceMethodTest::serviceWithArity0).aql("var");
- assertThat(expression).isEqualTo("aql:var.serviceWithArity0()");
+ String expression = ServiceMethod.of0(ArityFixture::instance0).aqlSelf();
+ assertThat(expression).isEqualTo("aql:self.instance0()");
+ expression = ServiceMethod.of0(ArityFixture::instance0).aql("var");
+ assertThat(expression).isEqualTo("aql:var.instance0()");
}
@Test
@DisplayName("GIVEN a service method with arity 0, WHEN an AQL expression is constructed with 1 parameter, THEN an exception is thrown")
public void givenServiceMethodWithArity0WhenAQLExpressionIsConstructedWith1ParameterThenExceptionIsThrown() {
- assertThatThrownBy(() -> ServiceMethod.of0(ServiceMethodTest::serviceWithArity0).aqlSelf("param1")).isInstanceOf(IllegalArgumentException.class);
- assertThatThrownBy(() -> ServiceMethod.of0(ServiceMethodTest::serviceWithArity0).aql("var", "param1")).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> ServiceMethod.of0(ArityFixture::instance0).aqlSelf("param1")).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> ServiceMethod.of0(ArityFixture::instance0).aql("var", "param1")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @DisplayName("GIVEN instance service factories, WHEN each arity is used, THEN the expected expressions are returned")
+ public void givenInstanceServiceFactoriesWhenEachArityIsUsedThenTheExpectedExpressionsAreReturned() throws NoSuchMethodException {
+ assertThat(ServiceMethod.of0(ArityFixture::instance0).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance0", Object.class));
+ assertThat(ServiceMethod.of1(ArityFixture::instance1).aql("ctx", AQL_VALUE)).isEqualTo("aql:ctx.instance1('value')");
+ assertThat(ServiceMethod.of2(ArityFixture::instance2).aqlSelf(AQL_VALUE, AQL_INT)).isEqualTo("aql:self.instance2('value',2)");
+ assertThat(ServiceMethod.of3(ArityFixture::instance3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.instance3('value',2,true)");
+ assertThat(ServiceMethod.of4(ArityFixture::instance4).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0"))
+ .isEqualTo("aql:self.instance4('value',2,true,4.0)");
+ assertThat(ServiceMethod.of5(ArityFixture::instance5).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0", "5L"))
+ .isEqualTo("aql:self.instance5('value',2,true,4.0,5L)");
+ assertThat(ServiceMethod.of6(ArityFixture::instance6).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0", "5L", "6.0f"))
+ .isEqualTo("aql:self.instance6('value',2,true,4.0,5L,6.0f)");
+ }
+
+ @Test
+ @DisplayName("GIVEN typed instance service factories, WHEN each overload is used, THEN the expected declarations are resolved")
+ public void givenTypedInstanceServiceFactoriesWhenEachOverloadIsUsedThenTheExpectedDeclarationsAreResolved() throws NoSuchMethodException {
+ assertThat(ServiceMethod.of0(Object.class, ArityFixture::instance0).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance0", Object.class));
+ assertThat(ServiceMethod.of0(ArityFixture.class, ArityFixture::instance0, Object.class).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance0", Object.class));
+ assertThat(ServiceMethod.of1(Object.class, String.class, ArityFixture::instance1).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance1", Object.class, String.class));
+ assertThat(ServiceMethod.of1(ArityFixture.class, ArityFixture::instance1, Object.class, String.class).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance1", Object.class, String.class));
+ assertThat(ServiceMethod.of2(Object.class, String.class, Integer.class, ArityFixture::instance2).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance2", Object.class, String.class, Integer.class));
+ assertThat(ServiceMethod.of2(ArityFixture.class, ArityFixture::instance2, Object.class, String.class, Integer.class).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance2", Object.class, String.class, Integer.class));
+ assertThat(ServiceMethod.of3(Object.class, String.class, Integer.class, Boolean.class, ArityFixture::instance3).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance3", Object.class, String.class, Integer.class, Boolean.class));
+ assertThat(ServiceMethod.of3(ArityFixture.class, ArityFixture::instance3, Object.class, String.class, Integer.class, Boolean.class).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance3", Object.class, String.class, Integer.class, Boolean.class));
+ assertThat(ServiceMethod.of4(Object.class, String.class, Integer.class, Boolean.class, Double.class, ArityFixture::instance4).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance4", Object.class, String.class, Integer.class, Boolean.class, Double.class));
+ assertThat(ServiceMethod.of4(ArityFixture.class, ArityFixture::instance4, Object.class, String.class, Integer.class, Boolean.class, Double.class).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance4", Object.class, String.class, Integer.class, Boolean.class, Double.class));
+ assertThat(ServiceMethod.of5(Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class, ArityFixture::instance5).declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance5", Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class));
+ assertThat(ServiceMethod.of5(ArityFixture.class, ArityFixture::instance5, Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class)
+ .declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance5", Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class));
+ assertThat(ServiceMethod.of6(Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class, Float.class, ArityFixture::instance6)
+ .declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance6", Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class, Float.class));
+ assertThat(ServiceMethod
+ .of6(ArityFixture.class, ArityFixture::instance6, Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class, Float.class)
+ .declaration())
+ .isEqualTo(ArityFixture.class.getDeclaredMethod("instance6", Object.class, String.class, Integer.class, Boolean.class, Double.class, Long.class, Float.class));
+ }
+
+ @Test
+ @DisplayName("GIVEN static service factories, WHEN each arity is used, THEN the expected expressions are returned")
+ public void givenStaticServiceFactoriesWhenEachArityIsUsedThenTheExpectedExpressionsAreReturned() throws NoSuchMethodException {
+ assertThat(ServiceMethod.ofStatic0(StaticFixture::static0).declaration())
+ .isEqualTo(StaticFixture.class.getDeclaredMethod("static0", Object.class));
+ assertThat(ServiceMethod.ofStatic0(Object.class, StaticFixture::static0).declaration())
+ .isEqualTo(StaticFixture.class.getDeclaredMethod("static0", Object.class));
+ assertThat(ServiceMethod.ofStatic1(StaticFixture::static1).aqlSelf(AQL_VALUE)).isEqualTo("aql:self.static1('value')");
+ assertThat(ServiceMethod.ofStatic1(Object.class, String.class, StaticFixture::static1).declaration())
+ .isEqualTo(StaticFixture.class.getDeclaredMethod("static1", Object.class, String.class));
+ assertThat(ServiceMethod.ofStatic2(StaticFixture::static2).aql("ctx", AQL_VALUE, AQL_INT)).isEqualTo("aql:ctx.static2('value',2)");
+ assertThat(ServiceMethod.ofStatic2(Object.class, String.class, Integer.class, StaticFixture::static2).declaration())
+ .isEqualTo(StaticFixture.class.getDeclaredMethod("static2", Object.class, String.class, Integer.class));
+ assertThat(ServiceMethod.ofStatic3(StaticFixture::static3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.static3('value',2,true)");
+ assertThat(ServiceMethod.ofStatic3(Object.class, String.class, Integer.class, Boolean.class, StaticFixture::static3).declaration())
+ .isEqualTo(StaticFixture.class.getDeclaredMethod("static3", Object.class, String.class, Integer.class, Boolean.class));
+ }
+
+ @Test
+ @DisplayName("GIVEN a service method with another arity, WHEN the wrong number of parameters is provided, THEN an exception is thrown")
+ public void givenServiceMethodWithAnotherArityWhenWrongNumberOfParametersIsProvidedThenExceptionIsThrown() {
+ assertThatThrownBy(() -> ServiceMethod.of3(ArityFixture::instance3).aqlSelf(AQL_VALUE, AQL_INT))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("instance3")
+ .hasMessageContaining("arity of 3");
+ }
+
+ @Test
+ @DisplayName("GIVEN an invalid AQL variable, WHEN aql is called, THEN an exception is thrown")
+ public void givenInvalidAQLVariableWhenAqlIsCalledThenExceptionIsThrown() {
+ assertThatThrownBy(() -> ServiceMethod.of0(ArityFixture::instance0).aql((String) null)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> ServiceMethod.of0(ArityFixture::instance0).aql("")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Test
+ @DisplayName("GIVEN wrong expected parameter types, WHEN the declaration is resolved, THEN an exception is thrown")
+ public void givenWrongExpectedParameterTypesWhenDeclarationIsResolvedThenAnExceptionIsThrown() {
+ ServiceMethod.Inst1 ref = ArityFixture::instance1;
+
+ assertThatThrownBy(() -> ServiceMethod.of1(Object.class, Integer.class, (ServiceMethod.Inst1) ref))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("expected");
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Test
+ @DisplayName("GIVEN a wrong expected service type, WHEN the declaration is resolved, THEN an exception is thrown")
+ public void givenWrongExpectedServiceTypeWhenDeclarationIsResolvedThenAnExceptionIsThrown() {
+ ServiceMethod.Inst1 ref = ArityFixture::instance1;
+
+ assertThatThrownBy(() -> ServiceMethod.of1((Class) WrongService.class, (ServiceMethod.Inst1) ref, Object.class, String.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("expected a service assignable");
+ }
+
+ @Test
+ @DisplayName("GIVEN overloaded services, WHEN explicit signature types are provided, THEN the expected declaration is resolved")
+ public void givenOverloadedServicesWhenExplicitSignatureTypesAreProvidedThenTheExpectedDeclarationIsResolved() throws NoSuchMethodException {
+ ServiceMethod serviceMethod = ServiceMethod.of1(OverloadedService.class, OverloadedService::overloaded, Object.class, Integer.class);
+
+ Method expectedDeclaration = OverloadedService.class.getDeclaredMethod("overloaded", Object.class, Integer.class);
+ assertThat(serviceMethod.name()).isEqualTo("overloaded");
+ assertThat(serviceMethod.declaration()).isEqualTo(expectedDeclaration);
+ assertThat(serviceMethod.declaration().getReturnType()).isEqualTo(Integer.class);
+ assertThat(serviceMethod.aqlSelf("42")).isEqualTo("aql:self.overloaded(42)");
}
- private Object serviceWithArity0(Object self) {
- return null;
+ @Test
+ @DisplayName("GIVEN overloaded services, WHEN explicit self type is provided, THEN arity-0 overloads can be selected")
+ public void givenOverloadedServicesWhenExplicitSelfTypeIsProvidedThenArity0OverloadsCanBeSelected() throws NoSuchMethodException {
+ ServiceMethod serviceMethod = ServiceMethod.of0(OverloadedService.class, OverloadedService::overloaded, String.class);
+
+ Method expectedDeclaration = OverloadedService.class.getDeclaredMethod("overloaded", String.class);
+ assertThat(serviceMethod.declaration()).isEqualTo(expectedDeclaration);
+ assertThat(serviceMethod.declaration().getReturnType()).isEqualTo(String.class);
+ assertThat(serviceMethod.aqlSelf()).isEqualTo("aql:self.overloaded()");
+ }
+
+ @Test
+ @DisplayName("GIVEN overloaded services, WHEN another explicit self type is provided, THEN the matching arity-0 overload is selected")
+ public void givenOverloadedServicesWhenAnotherExplicitSelfTypeIsProvidedThenTheMatchingArity0OverloadIsSelected() throws NoSuchMethodException {
+ ServiceMethod serviceMethod = ServiceMethod.of0(OverloadedService.class, OverloadedService::overloaded, Integer.class);
+
+ Method expectedDeclaration = OverloadedService.class.getDeclaredMethod("overloaded", Integer.class);
+ assertThat(serviceMethod.declaration()).isEqualTo(expectedDeclaration);
+ assertThat(serviceMethod.declaration().getReturnType()).isEqualTo(Integer.class);
+ assertThat(serviceMethod.aqlSelf()).isEqualTo("aql:self.overloaded()");
+ }
+
+ @Test
+ @DisplayName("GIVEN overloaded services, WHEN another explicit parameter type is provided, THEN the matching arity-1 overload is selected")
+ public void givenOverloadedServicesWhenAnotherExplicitParameterTypeIsProvidedThenTheMatchingArity1OverloadIsSelected() throws NoSuchMethodException {
+ ServiceMethod serviceMethod = ServiceMethod.of1(OverloadedService.class, OverloadedService::overloaded, Object.class, String.class);
+
+ Method expectedDeclaration = OverloadedService.class.getDeclaredMethod("overloaded", Object.class, String.class);
+ assertThat(serviceMethod.declaration()).isEqualTo(expectedDeclaration);
+ assertThat(serviceMethod.declaration().getReturnType()).isEqualTo(String.class);
+ assertThat(serviceMethod.aqlSelf(AQL_VALUE)).isEqualTo("aql:self.overloaded('value')");
+ }
+
+ /**
+ * Fixture used to validate factory methods across supported instance arities.
+ */
+ private static final class ArityFixture {
+ private Object instance0(Object self) {
+ return self;
+ }
+
+ private Object instance1(Object self, String p1) {
+ return p1;
+ }
+
+ private Object instance2(Object self, String p1, Integer p2) {
+ return p2;
+ }
+
+ private Object instance3(Object self, String p1, Integer p2, Boolean p3) {
+ return p3;
+ }
+
+ private Object instance4(Object self, String p1, Integer p2, Boolean p3, Double p4) {
+ return p4;
+ }
+
+ private Object instance5(Object self, String p1, Integer p2, Boolean p3, Double p4, Long p5) {
+ return p5;
+ }
+
+ private Object instance6(Object self, String p1, Integer p2, Boolean p3, Double p4, Long p5, Float p6) {
+ return p6;
+ }
+ }
+
+ /**
+ * Fixture used to validate factory methods for static service references.
+ */
+ private static final class StaticFixture {
+ private static Object static0(Object self) {
+ return self;
+ }
+
+ private static Object static1(Object self, String p1) {
+ return p1;
+ }
+
+ private static Object static2(Object self, String p1, Integer p2) {
+ return p2;
+ }
+
+ private static Object static3(Object self, String p1, Integer p2, Boolean p3) {
+ return p3;
+ }
+ }
+
+ /**
+ * Fixture used to validate service type mismatch checks.
+ */
+ private static final class WrongService {
+ private WrongService() {
+ // Utility fixture.
+ }
+ }
+
+ /**
+ * Fixture used to validate overloaded method resolution.
+ */
+ private static final class OverloadedService {
+ private String overloaded(String self) {
+ return self;
+ }
+
+ private Integer overloaded(Integer self) {
+ return self;
+ }
+
+ private String overloaded(Object self, String value) {
+ return value;
+ }
+
+ private Integer overloaded(Object self, Integer value) {
+ return value;
+ }
}
}
diff --git a/backend/tests/syson-tests/src/test/java/org/eclipse/syson/tests/GeneralPurposeTests.java b/backend/tests/syson-tests/src/test/java/org/eclipse/syson/tests/GeneralPurposeTests.java
index 1889cff1a..572455381 100644
--- a/backend/tests/syson-tests/src/test/java/org/eclipse/syson/tests/GeneralPurposeTests.java
+++ b/backend/tests/syson-tests/src/test/java/org/eclipse/syson/tests/GeneralPurposeTests.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2024, 2025 Obeo.
+ * Copyright (c) 2024, 2026 Obeo.
* 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
@@ -67,6 +67,8 @@ public class GeneralPurposeTests {
private static final String CHECKSTYLE_INTERFACE_IS_TYPE = "@SuppressWarnings(\"checkstyle:InterfaceIsType\")";
+ private static final String CHECKSTYLE_RAWTYPES_UNCHECKED = "@SuppressWarnings({ \"rawtypes\", \"unchecked\" })";
+
private static final String NON_NLS = "$NON-NLS-";
private static final String BUILDER = "Builder";
@@ -223,6 +225,8 @@ private void testNoSuppressWarnings(int index, String line, Path javaFilePath, L
isValidUsage = true;
} else if (line.contains(CHECKSTYLE_INTERFACE_IS_TYPE)) {
isValidUsage = true;
+ } else if (line.contains(CHECKSTYLE_RAWTYPES_UNCHECKED)) {
+ isValidUsage = true;
}
if (!isValidUsage) {
fail(this.createErrorMessage("@SuppressWarnings", javaFilePath, index));
diff --git a/doc/content/modules/developer-guide/pages/index.adoc b/doc/content/modules/developer-guide/pages/index.adoc
index c496a342a..1f4d4f290 100644
--- a/doc/content/modules/developer-guide/pages/index.adoc
+++ b/doc/content/modules/developer-guide/pages/index.adoc
@@ -549,6 +549,7 @@ When to use which call:
Type inference:
- if the compiler says something like _The type X does not define methodName(Object, Object,...)_, add a type witness to the factory so it can match the real signature.
+- if the service method is overloaded, use the factory overload that also takes the service class and Java parameter types to disambiguate the method reference.
Examples:
@@ -562,6 +563,9 @@ ServiceMethod. of1(DiagramMutation
// predicate isActor(Element)
ServiceMethod. of0(ModelQueryAQLService::isActor).aqlSelf();
+
+// overloaded EObjectServices::eGet(EObject, EStructuralFeature)
+ServiceMethod.of1(EObjectServices.class, EObjectServices::eGet, EObject.class, EStructuralFeature.class).aqlSelf(E_STRUCTURAL_FEATURE);
----
[#generate_openapi]