blob: 7bdaeeffff8b800bae062206bc4733169baefe1f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2022 Borland Software Corporation 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
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Borland Software Corporation - initial API and implementation
* Christopher Gerking - bug 289982
* Camille Letavernier - Bug 458651
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.blackbox.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.m2m.internal.qvt.oml.NLS;
import org.eclipse.m2m.internal.qvt.oml.QvtPlugin;
import org.eclipse.m2m.internal.qvt.oml.ast.env.InternalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalModuleEnv;
import org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil;
import org.eclipse.m2m.internal.qvt.oml.evaluator.ModuleInstance;
import org.eclipse.m2m.internal.qvt.oml.evaluator.NumberConversions;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtInterruptedExecutionException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtRuntimeException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.TransformationInstance;
import org.eclipse.m2m.internal.qvt.oml.stdlib.CallHandler;
import org.eclipse.m2m.internal.qvt.oml.stdlib.CallHandlerAdapter;
import org.eclipse.m2m.qvt.oml.blackbox.java.Operation;
import org.eclipse.ocl.types.OCLStandardLibrary;
class JavaMethodHandlerFactory {
private static int FAILURE_COUNT_TOLERANCE = 5;
final private Object fInvalid;
final private QvtOperationalModuleEnv fModuleEnv;
JavaMethodHandlerFactory(QvtOperationalModuleEnv moduleEnv) {
fInvalid = moduleEnv.getOCLStandardLibrary().getInvalid();
fModuleEnv = moduleEnv;
}
/**
* @deprecated Call {@link #JavaMethodHandlerFactory(QvtOperationalModuleEnv)} instead.
*/
@Deprecated
JavaMethodHandlerFactory(OCLStandardLibrary<EClassifier> oclStdLib) {
fInvalid = oclStdLib.getInvalid();
fModuleEnv = null;
}
CallHandler createHandler(Method method) {
if(method == null) {
throw new IllegalArgumentException();
}
Operation opAnnotation = method.getAnnotation(Operation.class);
return new Handler(method, opAnnotation != null && opAnnotation.contextual(),
opAnnotation != null && opAnnotation.withExecutionContext());
}
private Object getInvalidResult() {
return fInvalid;
}
private class Handler extends CallHandler {
private final Method fMethod;
private final Class<?>[] fCachedParamTypes;
private final boolean fIsContextual;
private final boolean fWithExecutionContext;
private final boolean fRequiresNumConversion;
private volatile int fFatalErrorCount;
Handler(Method method, boolean isContextual, boolean isWithExecutionContext) {
assert method != null;
fMethod = method;
fCachedParamTypes = fMethod.getParameterTypes();
fIsContextual = isContextual;
fWithExecutionContext = isWithExecutionContext;
fRequiresNumConversion = requiresNumberConversion();
fFatalErrorCount = 0;
}
@Override
public Object invoke(ModuleInstance module, Object source, Object[] args, QvtOperationalEvaluationEnv evalEnv) {
try {
if(isDisabled()) {
return getInvalidResult();
}
Object[] actualArgs = prepareArguments(source, args, evalEnv);
Object javaCallSource = null;
boolean isStatic = Modifier.isStatic(fMethod.getModifiers());
if(!isStatic) {
Class<?> moduleJavaClass = fMethod.getDeclaringClass();
javaCallSource = getJavaCallSource(module, moduleJavaClass, evalEnv); //module.getAdapter(moduleJavaClass);
assert javaCallSource != null;
}
return fMethod.invoke(javaCallSource, actualArgs);
}
catch (Throwable t) { // IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException
if (t instanceof InvocationTargetException) {
t = ((InvocationTargetException)t).getTargetException();
}
if (t instanceof OperationCanceledException) {
throw new QvtInterruptedExecutionException();
}
incrementFatalErrorCount();
QvtPlugin.error(NLS.bind(JavaBlackboxMessages.MethodInvocationError, fMethod), t);
String localized = "\nCaused by: " + t.getClass().getName() + //$NON-NLS-1$
(t.getLocalizedMessage() == null ? "" : ": " + t.getLocalizedMessage()); //$NON-NLS-1$ //$NON-NLS-2$
evalEnv.getAdapter(InternalEvaluationEnv.class).throwQVTException(
new QvtRuntimeException(NLS.bind(JavaBlackboxMessages.MethodInvocationError, fMethod) + localized, t));
return CallHandlerAdapter.getInvalidResult(evalEnv);
}
}
private void incrementFatalErrorCount(){
fFatalErrorCount++;
}
private Object getJavaCallSource(ModuleInstance moduleInstance, Class<?> javaClass, QvtOperationalEvaluationEnv evalEnv)
throws IllegalAccessException, InstantiationException {
Object callSource = moduleInstance.getAdapter(javaClass);
if (callSource != null) {
return callSource;
}
TransformationInstance rootTransformation = evalEnv.getRoot().getAdapter(InternalEvaluationEnv.class).getCurrentTransformation();
callSource = rootTransformation.getAdapter(javaClass);
if (callSource == null) {
callSource = javaClass.newInstance();
rootTransformation.getAdapter(ModuleInstance.Internal.class).addAdapter(callSource);
}
moduleInstance.getAdapter(ModuleInstance.Internal.class).addAdapter(callSource);
return callSource;
}
private boolean isDisabled() {
return fFatalErrorCount > FAILURE_COUNT_TOLERANCE;
}
private Object[] prepareArguments(Object source, Object[] args, QvtOperationalEvaluationEnv evalEnv) {
int argCount = args.length;
if (fIsContextual) {
argCount++;
}
if (fWithExecutionContext) {
argCount++;
}
Object resultArgs[] = new Object[argCount];
int argIndex = 0;
if (fWithExecutionContext) {
resultArgs[argIndex] = evalEnv.getContext();
argIndex++;
}
if (fIsContextual) {
// wrap dynamic EObject in proxy
if(source instanceof EObject) {
EObject eObject = (EObject) source;
if (EmfUtil.isDynamic(eObject) && !fCachedParamTypes[argIndex].isAssignableFrom(eObject.getClass())) {
if (fCachedParamTypes[argIndex].isInterface()) {
source = convertEObjectToProxy(eObject, fCachedParamTypes[argIndex]);
}
}
}
resultArgs[argIndex] = source;
argIndex++;
}
// filter out possible OclInvalid argument values passed from AST based evaluation
// source can't be this case as the call can not be made
for (int i = 0; i < args.length; i++) {
Object nextArg = args[i];
if(nextArg == getInvalidResult()) {
// convert OclInvalid to 'null' as java reflection invocation would fail
// with the argument class incompatible to the method signature
nextArg = null;
}
// number have to converted as java binary compatible
if(fRequiresNumConversion) {
nextArg = NumberConversions.convertNumber(nextArg, fCachedParamTypes[argIndex]);
}
// wrap dynamic EObject in proxy
if(nextArg instanceof EObject) {
EObject eObject = (EObject) nextArg;
if (EmfUtil.isDynamic(eObject) && !fCachedParamTypes[argIndex].isAssignableFrom(eObject.getClass())) {
if (fCachedParamTypes[argIndex].isInterface()) {
nextArg = convertEObjectToProxy(eObject, fCachedParamTypes[argIndex]);
}
}
}
resultArgs[argIndex++] = nextArg;
}
return resultArgs;
}
private boolean requiresNumberConversion() {
assert fMethod != null;
for (Class<?> paramType : fMethod.getParameterTypes()) {
if(Number.class.isAssignableFrom(paramType)) {
return true;
}
}
return false;
}
private Object convertEObjectToProxy(final EObject eObject, Class<?> proxyClass) {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
List<EOperation> operations = eObject.eClass().getEAllOperations();
for (EOperation operation : operations) {
if (isMatch(method, operation)) {
return eObject.eInvoke(operation, args == null ? ECollections.newBasicEList() : ECollections.newBasicEList(args));
}
}
List<EStructuralFeature> features = eObject.eClass().getEAllStructuralFeatures();
for (EStructuralFeature feature : features) {
if (method.getName().equalsIgnoreCase("get" + feature.getName())) { //$NON-NLS-1$
return eObject.eGet(feature);
}
else if (method.getName().equalsIgnoreCase("set" + feature.getName())) { //$NON-NLS-1$
Object newValue = feature.isMany() ? ECollections.newBasicEList(args) : args.length > 0 ? args[0] : null;
eObject.eSet(feature, newValue);
return null;
}
};
return method.invoke(eObject, args);
}
};
return Proxy.newProxyInstance(proxyClass.getClassLoader(), new Class<?>[] {proxyClass}, handler);
}
private boolean isMatch(Method method, EOperation operation) {
if (!operation.getName().equals(method.getName())) {
return false;
}
EClassifier eType = fModuleEnv.getUMLReflection().getOCLType(operation.getEType());
List<String> nsURIs;
Java2QVTTypeResolver typeResolver;
EClassifier eClassifier;
if (eType != null) {
nsURIs = Collections.singletonList(eType.getEPackage().getNsURI());
typeResolver = new Java2QVTTypeResolver(fModuleEnv, nsURIs, new BasicDiagnostic());
eClassifier = typeResolver.toEClassifier(method.getGenericReturnType(), Java2QVTTypeResolver.STRICT_TYPE);
if (eClassifier != eType) {
return false;
}
}
List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes());
List<EParameter> eParameters = operation.getEParameters();
if (parameterTypes.size() != eParameters.size()) {
return false;
}
Iterator<Class<?>> paramTypesIterator = parameterTypes.iterator();
for (EParameter param : eParameters) {
eType = fModuleEnv.getUMLReflection().getOCLType(param.getEType());
nsURIs = Collections.singletonList(eType.getEPackage().getNsURI());
Class<?> parameterType = paramTypesIterator.next();
typeResolver = new Java2QVTTypeResolver(fModuleEnv, nsURIs, new BasicDiagnostic());
eClassifier = typeResolver.toEClassifier(parameterType, Java2QVTTypeResolver.STRICT_TYPE);
if (eClassifier != eType) {
return false;
}
}
return true;
}
}
}