| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |