Skip to content

Commit ec82950

Browse files
committed
[enh] Support overloaded ServiceMethod references
Add overload-safe ServiceMethod factories that accept explicit Java signature types so ambiguous service method references can be resolved. Also store the resolved Method declaration instead of only the method name, add focused tests for overloaded services, and update the developer documentation with the new API usage. Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
1 parent de26422 commit ec82950

4 files changed

Lines changed: 473 additions & 30 deletions

File tree

backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java

Lines changed: 216 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package org.eclipse.syson.util;
1414

1515
import java.io.Serializable;
16+
import java.lang.invoke.MethodType;
1617
import java.lang.invoke.SerializedLambda;
1718
import java.lang.reflect.InvocationTargetException;
1819
import java.lang.reflect.Method;
@@ -72,6 +73,10 @@
7273
* }
7374
* </pre>
7475
* <p>
76+
* Overloaded services: if several service methods share the same name, use the factory overloads that also take the
77+
* service class and Java parameter types, for example
78+
* {@code ServiceMethod.of1(EObjectServices.class, EObjectServices::eGet, EObject.class, EStructuralFeature.class)}.
79+
* <p>
7580
* Performance: this uses reflection once per reference at startup to read a method name. The cost is negligible
7681
* compared to normal init work.
7782
*
@@ -80,12 +85,15 @@
8085
*/
8186
public final class ServiceMethod {
8287

88+
private final Method declaration;
89+
8390
private final String name;
8491

8592
private final int arity;
8693

87-
private ServiceMethod(String name, int arity) {
88-
this.name = name;
94+
private ServiceMethod(Method declaration, int arity) {
95+
this.declaration = declaration;
96+
this.name = declaration.getName();
8997
this.arity = arity;
9098
}
9199

@@ -98,6 +106,15 @@ public String name() {
98106
return this.name;
99107
}
100108

109+
/**
110+
* the Java declaration that will be called from AQL.
111+
*
112+
* @return the declaration.
113+
*/
114+
public Method declaration() {
115+
return this.declaration;
116+
}
117+
101118
/**
102119
* Build {@code aql:self.method(...)} for the captured service name.
103120
* <p>
@@ -149,79 +166,230 @@ public String aql(String var, String... params) {
149166
* Instance method with signature {@code R method(T self)}.
150167
*/
151168
public static <S, T> ServiceMethod of0(Inst0<S, T> ref) {
152-
return new ServiceMethod(methodName(ref), 0);
169+
return new ServiceMethod(method(ref), 0);
170+
}
171+
172+
/**
173+
* Instance method with signature {@code R method(T self)}.
174+
* <p>
175+
* Use this overload when the referenced Java service is overloaded and you need to disambiguate on the
176+
* {@code self} type.
177+
*/
178+
public static <S, T> ServiceMethod of0(Class<T> selfType, Inst0<S, T> ref) {
179+
return new ServiceMethod(method(ref, selfType), 0);
180+
}
181+
182+
/**
183+
* Instance method with signature {@code R method(T self)}.
184+
* <p>
185+
* Use this overload when the referenced Java service is overloaded and you need to disambiguate on the declaring
186+
* service and {@code self} types.
187+
*/
188+
public static <S, T> ServiceMethod of0(Class<S> serviceType, Inst0<S, T> ref, Class<T> selfType) {
189+
return new ServiceMethod(method(serviceType, ref, selfType), 0);
153190
}
154191

155192
/**
156193
* Instance method with signature {@code R method(T self, P1 p1)}.
157194
*/
158195
public static <S, T, P1> ServiceMethod of1(Inst1<S, T, P1> ref) {
159-
return new ServiceMethod(methodName(ref), 1);
196+
return new ServiceMethod(method(ref), 1);
197+
}
198+
199+
/**
200+
* Instance method with signature {@code R method(T self, P1 p1)}.
201+
* <p>
202+
* Use this overload when the referenced Java service is overloaded and you need to disambiguate on parameter
203+
* types.
204+
*/
205+
public static <S, T, P1> ServiceMethod of1(Class<T> selfType, Class<P1> p1Type, Inst1<S, T, P1> ref) {
206+
return new ServiceMethod(method(ref, selfType, p1Type), 1);
207+
}
208+
209+
/**
210+
* Instance method with signature {@code R method(T self, P1 p1)}.
211+
* <p>
212+
* Use this overload when the referenced Java service is overloaded and you need to disambiguate on the declaring
213+
* service and parameter types.
214+
*/
215+
public static <S, T, P1> ServiceMethod of1(Class<S> serviceType, Inst1<S, T, P1> ref, Class<T> selfType, Class<P1> p1Type) {
216+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type), 1);
160217
}
161218

162219
/**
163220
* Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
164221
*/
165222
public static <S, T, P1, P2> ServiceMethod of2(Inst2<S, T, P1, P2> ref) {
166-
return new ServiceMethod(methodName(ref), 2);
223+
return new ServiceMethod(method(ref), 2);
224+
}
225+
226+
/**
227+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
228+
*/
229+
public static <S, T, P1, P2> ServiceMethod of2(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Inst2<S, T, P1, P2> ref) {
230+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type), 2);
231+
}
232+
233+
/**
234+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2)}.
235+
*/
236+
public static <S, T, P1, P2> ServiceMethod of2(Class<S> serviceType, Inst2<S, T, P1, P2> ref, Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type) {
237+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type), 2);
167238
}
168239

169240
/**
170241
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
171242
*/
172243
public static <S, T, P1, P2, P3> ServiceMethod of3(Inst3<S, T, P1, P2, P3> ref) {
173-
return new ServiceMethod(methodName(ref), 3);
244+
return new ServiceMethod(method(ref), 3);
245+
}
246+
247+
/**
248+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
249+
*/
250+
public static <S, T, P1, P2, P3> ServiceMethod of3(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, Inst3<S, T, P1, P2, P3> ref) {
251+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type), 3);
252+
}
253+
254+
/**
255+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
256+
*/
257+
public static <S, T, P1, P2, P3> ServiceMethod of3(Class<S> serviceType, Inst3<S, T, P1, P2, P3> ref, Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type,
258+
Class<P3> p3Type) {
259+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type), 3);
174260
}
175261

176262
/**
177263
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
178264
*/
179265
public static <S, T, P1, P2, P3, P4> ServiceMethod of4(Inst4<S, T, P1, P2, P3, P4> ref) {
180-
return new ServiceMethod(methodName(ref), 4);
266+
return new ServiceMethod(method(ref), 4);
267+
}
268+
269+
/**
270+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
271+
*/
272+
public static <S, T, P1, P2, P3, P4> ServiceMethod of4(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type,
273+
Inst4<S, T, P1, P2, P3, P4> ref) {
274+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type), 4);
275+
}
276+
277+
/**
278+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4)}.
279+
*/
280+
public static <S, T, P1, P2, P3, P4> ServiceMethod of4(Class<S> serviceType, Inst4<S, T, P1, P2, P3, P4> ref, Class<T> selfType, Class<P1> p1Type,
281+
Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type) {
282+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type), 4);
181283
}
182284

183285
/**
184286
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
185287
*/
186288
public static <S, T, P1, P2, P3, P4, P5> ServiceMethod of5(Inst5<S, T, P1, P2, P3, P4, P5> ref) {
187-
return new ServiceMethod(methodName(ref), 5);
289+
return new ServiceMethod(method(ref), 5);
290+
}
291+
292+
/**
293+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
294+
*/
295+
public static <S, T, P1, P2, P3, P4, P5> ServiceMethod of5(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type,
296+
Class<P5> p5Type, Inst5<S, T, P1, P2, P3, P4, P5> ref) {
297+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type), 5);
188298
}
189299

300+
/**
301+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)}.
302+
*/
303+
// CHECKSTYLE:OFF
304+
public static <S, T, P1, P2, P3, P4, P5> ServiceMethod of5(Class<S> serviceType, Inst5<S, T, P1, P2, P3, P4, P5> ref, Class<T> selfType, Class<P1> p1Type,
305+
Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type, Class<P5> p5Type) {
306+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type), 5);
307+
}
308+
// CHECKSTYLE:ON
309+
190310
/**
191311
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
192312
*/
193313
public static <S, T, P1, P2, P3, P4, P5, P6> ServiceMethod of6(Inst6<S, T, P1, P2, P3, P4, P5, P6> ref) {
194-
return new ServiceMethod(methodName(ref), 6);
314+
return new ServiceMethod(method(ref), 6);
315+
}
316+
317+
/**
318+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
319+
*/
320+
// CHECKSTYLE:OFF
321+
public static <S, T, P1, P2, P3, P4, P5, P6> ServiceMethod of6(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type,
322+
Class<P5> p5Type, Class<P6> p6Type, Inst6<S, T, P1, P2, P3, P4, P5, P6> ref) {
323+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type, p6Type), 6);
195324
}
325+
// CHECKSTYLE:ON
326+
327+
/**
328+
* Instance method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)}.
329+
*/
330+
// CHECKSTYLE:OFF
331+
public static <S, T, P1, P2, P3, P4, P5, P6> ServiceMethod of6(Class<S> serviceType, Inst6<S, T, P1, P2, P3, P4, P5, P6> ref, Class<T> selfType,
332+
Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, Class<P4> p4Type, Class<P5> p5Type, Class<P6> p6Type) {
333+
return new ServiceMethod(method(serviceType, ref, selfType, p1Type, p2Type, p3Type, p4Type, p5Type, p6Type), 6);
334+
}
335+
// CHECKSTYLE:ON
196336

197337
// ---------------------- Factories for static methods ----------------------
198338

199339
/**
200340
* Static method with signature {@code R method(T self)}.
201341
*/
202342
public static <T> ServiceMethod ofStatic0(IStat0<T> ref) {
203-
return new ServiceMethod(methodName(ref), 0);
343+
return new ServiceMethod(method(ref), 0);
344+
}
345+
346+
/**
347+
* Static method with signature {@code R method(T self)}.
348+
*/
349+
public static <T> ServiceMethod ofStatic0(Class<T> selfType, IStat0<T> ref) {
350+
return new ServiceMethod(method(ref, selfType), 0);
204351
}
205352

206353
/**
207354
* Static method with signature {@code R method(T self, P1 p1)}.
208355
*/
209356
public static <T, P1> ServiceMethod ofStatic1(IStat1<T, P1> ref) {
210-
return new ServiceMethod(methodName(ref), 1);
357+
return new ServiceMethod(method(ref), 1);
358+
}
359+
360+
/**
361+
* Static method with signature {@code R method(T self, P1 p1)}.
362+
*/
363+
public static <T, P1> ServiceMethod ofStatic1(Class<T> selfType, Class<P1> p1Type, IStat1<T, P1> ref) {
364+
return new ServiceMethod(method(ref, selfType, p1Type), 1);
211365
}
212366

213367
/**
214368
* Static method with signature {@code R method(T self, P1 p1, P2 p2)}.
215369
*/
216370
public static <T, P1, P2> ServiceMethod ofStatic2(IStat2<T, P1, P2> ref) {
217-
return new ServiceMethod(methodName(ref), 2);
371+
return new ServiceMethod(method(ref), 2);
372+
}
373+
374+
/**
375+
* Static method with signature {@code R method(T self, P1 p1, P2 p2)}.
376+
*/
377+
public static <T, P1, P2> ServiceMethod ofStatic2(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, IStat2<T, P1, P2> ref) {
378+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type), 2);
218379
}
219380

220381
/**
221382
* Static method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
222383
*/
223384
public static <T, P1, P2, P3> ServiceMethod ofStatic3(IStat3<T, P1, P2, P3> ref) {
224-
return new ServiceMethod(methodName(ref), 3);
385+
return new ServiceMethod(method(ref), 3);
386+
}
387+
388+
/**
389+
* Static method with signature {@code R method(T self, P1 p1, P2 p2, P3 p3)}.
390+
*/
391+
public static <T, P1, P2, P3> ServiceMethod ofStatic3(Class<T> selfType, Class<P1> p1Type, Class<P2> p2Type, Class<P3> p3Type, IStat3<T, P1, P2, P3> ref) {
392+
return new ServiceMethod(method(ref, selfType, p1Type, p2Type, p3Type), 3);
225393
}
226394

227395
// ---------------------- SAMs for method references ----------------------
@@ -427,14 +595,42 @@ public interface IStat3<T, P1, P2, P3> extends Serializable {
427595

428596
// ---------------------- Lambda -> method name ----------------------
429597

430-
private static String methodName(Serializable lambdaRef) {
598+
private static Method method(Serializable lambdaRef, Class<?>... expectedParameterTypes) {
599+
try {
600+
SerializedLambda lambda = serializedLambda(lambdaRef);
601+
Class<?> implementationClass = Class.forName(lambda.getImplClass().replace('/', '.'), false, lambdaRef.getClass().getClassLoader());
602+
MethodType methodType = MethodType.fromMethodDescriptorString(lambda.getImplMethodSignature(), implementationClass.getClassLoader());
603+
Method method = thisClassMethod(implementationClass, lambda.getImplMethodName(), methodType.parameterArray());
604+
if (expectedParameterTypes.length > 0 && !Arrays.equals(method.getParameterTypes(), expectedParameterTypes)) {
605+
throw new IllegalArgumentException(
606+
MessageFormat.format("Resolved method {0} has parameters {1} but expected {2}", method, Arrays.toString(method.getParameterTypes()), Arrays.toString(expectedParameterTypes)));
607+
}
608+
return method;
609+
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
610+
throw new IllegalStateException("Cannot resolve method declaration from lambda", e);
611+
}
612+
}
613+
614+
private static Method method(Class<?> expectedServiceType, Serializable lambdaRef, Class<?>... expectedParameterTypes) {
615+
Method method = method(lambdaRef, expectedParameterTypes);
616+
if (!expectedServiceType.isAssignableFrom(method.getDeclaringClass())) {
617+
throw new IllegalArgumentException(MessageFormat.format("Resolved method {0} is declared on {1} but expected a service assignable to {2}", method,
618+
method.getDeclaringClass().getName(), expectedServiceType.getName()));
619+
}
620+
return method;
621+
}
622+
623+
private static SerializedLambda serializedLambda(Serializable lambdaRef) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
624+
Method writeReplace = lambdaRef.getClass().getDeclaredMethod("writeReplace");
625+
writeReplace.setAccessible(true);
626+
return (SerializedLambda) writeReplace.invoke(lambdaRef);
627+
}
628+
629+
private static Method thisClassMethod(Class<?> implementationClass, String methodName, Class<?>[] parameterTypes) throws NoSuchMethodException {
431630
try {
432-
Method m = lambdaRef.getClass().getDeclaredMethod("writeReplace");
433-
m.setAccessible(true);
434-
SerializedLambda sl = (SerializedLambda) m.invoke(lambdaRef);
435-
return sl.getImplMethodName();
436-
} catch (InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
437-
throw new IllegalStateException("Cannot resolve method name from lambda", e);
631+
return implementationClass.getDeclaredMethod(methodName, parameterTypes);
632+
} catch (NoSuchMethodException exception) {
633+
return implementationClass.getMethod(methodName, parameterTypes);
438634
}
439635
}
440636

@@ -457,4 +653,3 @@ private void checkArity(String... params) {
457653
}
458654
}
459655
}
460-

0 commit comments

Comments
 (0)