blob: d2e7c47f7b6c12b8103df24a0d58587b680c1120 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2019 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.ecore.EClassifier;
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.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;
JavaMethodHandlerFactory(OCLStandardLibrary<EClassifier> oclStdLib) {
fInvalid = oclStdLib.getInvalid();
}
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) {
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]);
}
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;
}
}
}