/******************************************************************************* * Copyright (c) 2011 Andr� Arnold and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.eclipse.xtend.backend.lib; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.xtend.backend.common.BackendType; import org.eclipse.xtend.backend.common.BackendTypesystem; import org.eclipse.xtend.backend.common.ExecutionContext; import org.eclipse.xtend.backend.common.NamedFunction; import org.eclipse.xtend.backend.common.QualifiedName; import org.eclipse.xtend.backend.common.SourcePos; import org.eclipse.xtend.backend.functions.java.internal.JavaBuiltinConverter; import org.eclipse.xtend.backend.functions.java.internal.JavaBuiltinConverterFactory; import org.eclipse.xtend.backend.functions.java.internal.ParameterConverter; import org.eclipse.xtend.backend.syslib.CollectionOperations; import org.eclipse.xtend.backend.util.ErrorHandler; import org.eclipse.xtend.middleend.javaannotations.ExecutionContextAware; /** * @author aarnold - Initial contribution and API */ public class Invoker { public static Object invokeOnObject (final QualifiedName functionName, final List<Object> params, ExecutionContext ctx, final boolean firstParamIsThis) { // this is for "method-style" invocations: if the first parameter (i.e. the one "before the dot") is null, // shortcut the evaluation and return null // // CAUTION - without this shortcut, the polymorphic resolution may be ambiguous in unexpected ways return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); } public static Object invokeOnObjectNullChecked (final QualifiedName functionName, final List<Object> params, ExecutionContext ctx, final boolean firstParamIsThis, final SourcePos pos) { // this is for "method-style" invocations: if the first parameter (i.e. the one "before the dot") is null, // shortcut the evaluation and return null // // CAUTION - without this shortcut, the polymorphic resolution may be ambiguous in unexpected ways if (params.size() > 0 && params.get(0) == null) { ctx.logNullDeRef (pos); return null; } return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); } public static Object invokeOnCollection (final QualifiedName functionName, final Collection<?> coll, final List<Object> params, ExecutionContext ctx, final boolean firstParamIsThis, final SourcePos pos) { if (coll == null) { ctx.logNullDeRef (pos); return null; } final Collection<Object> result = CollectionOperations.createMatchingCollection (coll); for (Object o: coll) { params.set (0, o); CollectionOperations.addFlattened (result, ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis)); } return result; } public static Object invokeOnWhatEver (final QualifiedName functionName, final List<Object> params, ExecutionContext ctx, final boolean firstParamIsThis) { if (params.get (0) instanceof Collection<?>) { // check if this is a function on Collection itself if (ctx.getFunctionDefContext().hasMatch (ctx, functionName, params)) return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); final Collection<?> coll = (Collection<?>) params.get (0); final Collection<Object> result = CollectionOperations.createMatchingCollection (coll); for (Object o: coll) { params.set (0, o); CollectionOperations.addFlattened (result, ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis)); } return result; } else return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); } public static Object invokeOnWhatEverNullChecked (final QualifiedName functionName, final List<Object> params, ExecutionContext ctx, final boolean firstParamIsThis, final SourcePos pos) { if (params.get(0) == null) { ctx.logNullDeRef (pos); return null; } if (params.get (0) instanceof Collection<?>) { // check if this is a function on Collection itself if (ctx.getFunctionDefContext().hasMatch (ctx, functionName, params)) return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); final Collection<?> coll = (Collection<?>) params.get (0); final Collection<Object> result = CollectionOperations.createMatchingCollection (coll); for (Object o: coll) { params.set (0, o); CollectionOperations.addFlattened (result, ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis)); } return result; } else return ctx.getFunctionDefContext().invoke (ctx, functionName, params, firstParamIsThis); } public static Object invokeOnObject (final NamedFunction function, final List<Object> params, ExecutionContext ctx, final boolean nullIfFirstParamIsNull, final boolean firstParamIsThis, final SourcePos pos) { // this is for "method-style" invocations: if the first parameter (i.e. the one "before the dot") is null, // shortcut the evaluation and return null // // CAUTION - without this shortcut, the polymorphic resolution may be ambiguous in unexpected ways if (nullIfFirstParamIsNull && params.size() > 0 && params.get(0) == null) { ctx.logNullDeRef (pos); return null; } return ctx.getFunctionInvoker().invoke (ctx, function.getFunction(), params); } public static Object invokeOnCollection (final NamedFunction function, final Collection<?> coll, final List<Object> params, ExecutionContext ctx, final boolean nullIfFirstParamIsNull, final boolean firstParamIsThis, final SourcePos pos) { if (coll == null) { ctx.logNullDeRef (pos); return null; } final Collection<Object> result = CollectionOperations.createMatchingCollection (coll); for (Object o: coll) { params.set (0, o); CollectionOperations.addFlattened (result, ctx.getFunctionInvoker().invoke (ctx, function.getFunction(), params)); } return result; } public static Object invokeMethod (Method method, List<Object> params, final boolean isStatic, ExecutionContext ctx, final boolean nullIfFirstParamIsNull, final boolean firstParamIsThis, final SourcePos pos) { // this is for "method-style" invocations: if the first parameter (i.e. // the one "before the dot") is null, // shortcut the evaluation and return null // // CAUTION - without this shortcut, the polymorphic resolution may be // ambiguous in unexpected ways if (nullIfFirstParamIsNull && params.size() > 0 && params.get(0) == null) { ctx.logNullDeRef(pos); return null; } Object[] rawParams = params.toArray(new Object[params.size()]); try { // param Expressions must have been evaluated before method can fully qualified with it's param types final List<ParameterConverter> parameterConverters = getParameterConverters(method); final JavaBuiltinConverter returnValueConverter = getReturnValueConverter (method); for (int i = 0; i < parameterConverters.size(); i++) { parameterConverters.get(i).convert(rawParams); } Object o = getInstance(ctx, method, isStatic); if (o instanceof ExecutionContextAware) { ((ExecutionContextAware) o).setExecutionContext(ctx); } final Object resultRaw = method.invoke(o, rawParams); return returnValueConverter.javaToBackend(resultRaw); } catch (Exception e) { final List<String> paramTypes = new ArrayList<String>(); for (Object p : params) { if (p == null) paramTypes.add(Void.TYPE.getName()); else paramTypes.add(p.getClass().getName()); } ErrorHandler.handle("could not invoke method " + method + " with parameters " + params + " of types " + paramTypes, e); return null; // to make the compiler happy - this is never executed } } private static Object getInstance(ExecutionContext ctx, Method method, boolean isStatic) { final Class<?> clazz = method.getDeclaringClass(); if (isStatic) return null; try { Object result = ctx.getContributionStateContext().retrieveState( clazz); if (result == null) { result = clazz.newInstance(); ctx.getContributionStateContext().storeState(clazz, result); } return result; } catch (Exception exc) { ErrorHandler.handle(exc); return null; // just for the compiler - this is never executed } } public static List<BackendType> guessParameterTypes(Method mtd, BackendTypesystem ts) { final List<BackendType> result = new ArrayList<BackendType>(); for (Class<?> cls : mtd.getParameterTypes()) result.add(ts.findType(cls)); return result; } private static List<ParameterConverter> getParameterConverters (Method mtd) { List<ParameterConverter> parameterConverters = new ArrayList<ParameterConverter> (mtd.getParameterTypes().length); for (int i=0; i<mtd.getParameterTypes().length; i++) { final ParameterConverter pc = JavaBuiltinConverterFactory.getParameterConverter (mtd.getParameterTypes()[i], i); if (pc != null) parameterConverters.add(pc); } return parameterConverters; } private static JavaBuiltinConverter getReturnValueConverter(Method mtd) { JavaBuiltinConverter converter = JavaBuiltinConverterFactory.getConverter (mtd.getReturnType()); return converter; } }