-
Notifications
You must be signed in to change notification settings - Fork 3
/
EOperationService.java
357 lines (316 loc) · 11.4 KB
/
EOperationService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*******************************************************************************
* Copyright (c) 2016, 2023 Obeo.
* 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
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.query.runtime.impl;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.acceleo.query.parser.CombineIterator;
import org.eclipse.acceleo.query.runtime.AcceleoQueryValidationException;
import org.eclipse.acceleo.query.runtime.ICompletionProposal;
import org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment;
import org.eclipse.acceleo.query.runtime.impl.completion.EOperationServiceCompletionProposal;
import org.eclipse.acceleo.query.validation.type.ClassType;
import org.eclipse.acceleo.query.validation.type.EClassifierLiteralType;
import org.eclipse.acceleo.query.validation.type.EClassifierType;
import org.eclipse.acceleo.query.validation.type.IJavaType;
import org.eclipse.acceleo.query.validation.type.IType;
import org.eclipse.acceleo.query.validation.type.SequenceType;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EcorePackage;
/**
* Abstract implementation of an {@link org.eclipse.acceleo.query.runtime.IService IService} for
* {@link EOperation}.
*
* @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
* @since 4.1
*/
public class EOperationService extends AbstractService<EOperation> {
/**
* The {@link org.eclipse.acceleo.query.runtime.IService#getPriority() priority} for
* {@link EOperationService}.
*/
public static final int PRIORITY = 100;
/**
* Log message used when a called EOperation can't be invoked.
*/
protected static final String COULDN_T_INVOKE_EOPERATION = "Couldn't invoke the %s EOperation (%s)";
/**
* The Java method which actually implements the EOperation.
*/
private final Method method;
/**
* Creates a new service instance given a method and an instance.
*
* @param eOperation
* the {@link EOperation} that realizes the service
*/
public EOperationService(EOperation eOperation) {
super(eOperation);
this.method = lookupMethod(eOperation);
}
/**
* Finds the Java {@link Method} which implements a given {@link EOperation}.
*
* @param operation
* the {@link EOperation} to look for.
* @return the Java method which implements the {@link EOperation}, or <code>null</code> if none could be
* found.
*/
private Method lookupMethod(EOperation operation) {
Method result;
final Class<?> containerClass = operation.getEContainingClass().getInstanceClass();
if (containerClass != null) {
final Class<?>[] argumentClasses = new Class<?>[operation.getEParameters().size()];
for (int i = 0; i < argumentClasses.length; i++) {
EParameter param = operation.getEParameters().get(i);
if (param.isMany()) {
argumentClasses[i] = EList.class;
} else {
argumentClasses[i] = param.getEType().getInstanceClass();
}
}
try {
result = containerClass.getMethod(operation.getName(), argumentClasses);
} catch (SecurityException e) {
result = null;
} catch (NoSuchMethodException e) {
result = null;
}
} else {
result = null;
}
return result;
}
@Override
public String getName() {
return getOrigin().getName();
}
@Override
public List<IType> computeParameterTypes(IReadOnlyQueryEnvironment queryEnvironment) {
final List<IType> result = new ArrayList<IType>();
result.add(new EClassifierType(queryEnvironment, getOrigin().getEContainingClass()));
for (EParameter parameter : getOrigin().getEParameters()) {
final EClassifierType rawType = new EClassifierType(queryEnvironment, parameter.getEType());
if (parameter.isMany()) {
result.add(new SequenceType(queryEnvironment, rawType));
} else {
result.add(rawType);
}
}
return result;
}
@Override
public int getNumberOfParameters() {
return getOrigin().getEParameters().size() + 1;
}
@Override
protected Object internalInvoke(Object[] arguments) throws Exception {
final Object result;
final EObject receiver = (EObject)arguments[0];
final Object[] localArguments = new Object[arguments.length];
for (int i = 1; i < arguments.length; ++i) {
if (getOrigin().getEParameters().get(i - 1).isMany()) {
localArguments[i] = new BasicEList<Object>((Collection<?>)arguments[i]);
} else {
localArguments[i] = arguments[i];
}
}
if (!getOrigin().getEContainingClass().isSuperTypeOf(receiver.eClass())) {
if (method != null) {
final Object[] parameters = Arrays.copyOfRange(localArguments, 1, localArguments.length);
result = eOperationJavaInvoke(method, receiver, parameters);
} else {
throw new IllegalStateException(String.format(
"EOperation %s not in %s type hierarchy of %s and no %s method in %s", getName(),
getOrigin().getEContainingClass().getName(), receiver.eClass().getName(), getName(),
receiver.getClass().getName()));
}
} else if (hasEInvoke(receiver)) {
final EList<Object> eArguments = new BasicEList<Object>(localArguments.length);
for (int i = 1; i < localArguments.length; ++i) {
eArguments.add(localArguments[i]);
}
result = receiver.eInvoke(getOrigin(), eArguments);
} else if (method != null) {
final Object[] parameters = Arrays.copyOfRange(localArguments, 1, localArguments.length);
result = eOperationJavaInvoke(method, receiver, parameters);
} else {
throw new IllegalStateException(String.format("No eInvoke nor %s methods in %s", getName(),
receiver.getClass().getName()));
}
return result;
}
/**
* Try to find out if the Operation reflection is enable for the given {@link Object}.
*
* @param object
* the {@link Object} to test.
* @return <code>true</code> if the Operation reflection is enable for the given {@link Object},
* <code>false</code> otherwise
*/
private boolean hasEInvoke(Object object) {
Method eInvokeMethod = null;
try {
eInvokeMethod = object.getClass().getDeclaredMethod("eInvoke", int.class, EList.class);
} catch (NoSuchMethodException e) {
// nothing to do here
} catch (SecurityException e) {
// nothing to do here
}
return eInvokeMethod != null;
}
/**
* Call the {@link EOperation} thru a Java invoke.
*
* @param eInvokeMethod
* the {@link Method}
* @param receiver
* the receiver
* @param arguments
* arguments
* @return the {@link EOperation} result if any, {@link Nothing} otherwise
* @throws Exception
* if the invoked {@link EOperation} fail
*/
private Object eOperationJavaInvoke(Method eInvokeMethod, final Object receiver, final Object[] arguments)
throws Exception {
if (eInvokeMethod != null && receiver != null) {
return eInvokeMethod.invoke(receiver, arguments);
} else {
throw new IllegalArgumentException();
}
}
@Override
public int getPriority() {
return PRIORITY;
}
@Override
public Set<IType> computeType(IReadOnlyQueryEnvironment queryEnvironment) {
final Set<IType> result = new LinkedHashSet<IType>();
final IType eClassifierType = new EClassifierType(queryEnvironment, getOrigin().getEType());
if (getOrigin().isMany()) {
result.add(new SequenceType(queryEnvironment, eClassifierType));
} else {
result.add(eClassifierType);
}
return result;
}
@Override
public boolean matches(IReadOnlyQueryEnvironment queryEnvironment, IType[] argumentTypes) {
final List<Set<IType>> eClassifierTypes = new ArrayList<Set<IType>>(argumentTypes.length);
boolean canMatch = true;
for (int i = 0; i < argumentTypes.length; ++i) {
Set<EClassifier> eClassifiers;
final IType iType = argumentTypes[i];
if (iType instanceof EClassifierLiteralType) {
eClassifiers = new LinkedHashSet<EClassifier>();
eClassifiers.add(EcorePackage.eINSTANCE.getEClass());
} else if (iType instanceof EClassifierType) {
eClassifiers = new LinkedHashSet<EClassifier>();
eClassifiers.add(((EClassifierType)iType).getType());
} else if (iType instanceof SequenceType) {
eClassifiers = new LinkedHashSet<EClassifier>();
eClassifiers.add(EcorePackage.eINSTANCE.getEEList());
} else if (iType instanceof IJavaType) {
if (iType.getType() == null) {
eClassifiers = new LinkedHashSet<EClassifier>();
eClassifiers.add(null);
} else if (List.class.isAssignableFrom(((IJavaType)iType).getType())) {
eClassifiers = new LinkedHashSet<EClassifier>();
eClassifiers.add(EcorePackage.eINSTANCE.getEEList());
} else {
eClassifiers = queryEnvironment.getEPackageProvider().getEClassifiers(((IJavaType)iType)
.getType());
if (eClassifiers == null) {
canMatch = false;
break;
}
}
} else {
throw new AcceleoQueryValidationException(iType.getClass().getCanonicalName());
}
final Set<IType> types = new LinkedHashSet<IType>();
for (EClassifier eClassifier : eClassifiers) {
if (eClassifier != null) {
types.add(new EClassifierType(queryEnvironment, eClassifier));
} else {
types.add(new ClassType(queryEnvironment, null));
}
}
eClassifierTypes.add(types);
}
if (canMatch) {
CombineIterator<IType> it = new CombineIterator<IType>(eClassifierTypes);
boolean matched = false;
while (it.hasNext()) {
final List<IType> parameterTypes = it.next();
if (super.matches(queryEnvironment, parameterTypes.toArray(new IType[parameterTypes
.size()]))) {
matched = true;
break;
}
}
canMatch = matched;
}
return canMatch;
}
@Override
public String getShortSignature() {
final List<IType> parameterTypes = getParameterTypes(null);
final IType[] argumentTypes = parameterTypes.toArray(new IType[parameterTypes.size()]);
return serviceShortSignature(argumentTypes);
}
@Override
public String getLongSignature() {
final String ePkgNsURI;
final String eCLassName;
final EClass eContainingClass = getOrigin().getEContainingClass();
if (eContainingClass != null) {
eCLassName = eContainingClass.getName();
final EPackage ePackage = eContainingClass.getEPackage();
if (ePackage != null) {
ePkgNsURI = ePackage.getNsURI();
} else {
ePkgNsURI = null;
}
} else {
ePkgNsURI = null;
eCLassName = null;
}
return ePkgNsURI + " " + eCLassName + " " + getShortSignature();
}
@Override
public boolean equals(Object obj) {
return obj instanceof EOperationService && ((EOperationService)obj).getOrigin().equals(getOrigin());
}
@Override
public int hashCode() {
return getOrigin().hashCode();
}
@Override
public List<ICompletionProposal> getProposals(IReadOnlyQueryEnvironment queryEnvironment,
Set<IType> receiverTypes) {
final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
result.add(new EOperationServiceCompletionProposal(getOrigin()));
return result;
}
}