blob: 221f59a750160b2c7a75de4049d6f50e4f12545d [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2018 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.eol.execute.operations;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.Arrays;
import java.util.Collection;
import org.eclipse.epsilon.common.util.StringUtil;
import org.eclipse.epsilon.eol.dom.Expression;
import org.eclipse.epsilon.eol.dom.NameExpression;
import org.eclipse.epsilon.eol.dom.Parameter;
import org.eclipse.epsilon.eol.exceptions.EolIllegalOperationException;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.introspection.java.ObjectMethod;
import org.eclipse.epsilon.eol.function.EolLambdaFactory;
import org.eclipse.epsilon.eol.types.EolNoType;
import org.eclipse.epsilon.eol.util.ReflectionUtil;
/**
* Provides a bridge between EOL first-order syntax and Java lambdas
* (i.e. {@linkplain FunctionalInterface}s).
*
* @author Sina Madani
* @since 1.6
*/
public class DynamicOperation extends AbstractOperation {
@Override
public Object execute(Object target, NameExpression operationNameExpression, List<Parameter> iterators, List<Expression> expressions, IEolContext context) throws EolRuntimeException {
assert expressions != null && !expressions.isEmpty();
LinkedHashMap<Expression, List<Parameter>> lambdas = new LinkedHashMap<>(expressions.size(), 1f);
for (Expression expression : expressions) {
lambdas.put(expression, iterators);
}
return execute(target, operationNameExpression, lambdas, context);
}
public Object execute(Object target, NameExpression operationNameExpression, LinkedHashMap<Expression, List<Parameter>> lambdas, IEolContext context) throws EolRuntimeException {
final Iterator<Map.Entry<Expression, List<Parameter>>> entriesIter = lambdas.entrySet().iterator();
final Collection<Expression> expressions = lambdas.keySet();
final String methodName = operationNameExpression.getName();
if (target instanceof EolNoType || target instanceof EolLambdaFactory) {
final Map.Entry<Expression, List<Parameter>> first = entriesIter.next();
return EolLambdaFactory.resolveFor(methodName, first.getValue(), first.getKey(), operationNameExpression, context);
}
// Look for a matching method with FunctionalInterface parameter(s)
Predicate<Method> criteria = method ->
Modifier.isPublic(method.getModifiers()) &&
method.getParameterCount() == expressions.size() &&
Arrays.stream(method.getParameterTypes())
.filter(Class::isInterface)
.allMatch(intf -> Arrays.stream(intf.getAnnotations())
.anyMatch(a -> a.annotationType().equals(FunctionalInterface.class)) ||
Arrays.stream(intf.getMethods())
.filter(targetParamMethod -> Modifier.isAbstract(targetParamMethod.getModifiers()))
.count() == 1
);
final Method resolvedMethod = ReflectionUtil.findApplicableMethodOrThrow(
target, methodName, criteria, expressions, operationNameExpression, context.getPrettyPrinterManager()
);
final int candidateParamCount = resolvedMethod.getParameterCount();
final Object[] candidateParameterValues = new Object[candidateParamCount];
final java.lang.reflect.Parameter[] candidateParameterTypes = resolvedMethod.getParameters();
for (int i = 0; i < candidateParamCount && entriesIter.hasNext(); i++) {
final Class<?> targetType = candidateParameterTypes[i].getType();
final Map.Entry<Expression, List<Parameter>> complexExpr = entriesIter.next();
final Expression rawExpr = complexExpr.getKey();
if (!targetType.isInterface() || complexExpr.getValue() == null) {
candidateParameterValues[i] = rawExpr.execute(context);
continue;
}
final List<Parameter> iteratorParams = normalizeParameters(complexExpr.getValue());
try {
// First try to use the CheckedEol version of known functional interfaces
candidateParameterValues[i] = EolLambdaFactory.resolveFor(targetType, iteratorParams, rawExpr, operationNameExpression, context);
}
catch (EolIllegalOperationException eox) {
final IEolContext fContext = context;
// Failing that, try to implement the interface
candidateParameterValues[i] = Proxy.newProxyInstance(
targetType.getClassLoader(),
new Class[]{targetType},
(proxy, method, args) ->
EolLambdaFactory.executeExpression(fContext, operationNameExpression, null, rawExpr, iteratorParams, args)
);
}
}
// Finally, call the method with the resolved parameters
try (ObjectMethod om = new ObjectMethod(target, resolvedMethod)) {
return om.execute(operationNameExpression, context, candidateParameterValues);
}
}
static List<Parameter> normalizeParameters(List<Parameter> iterators) {
if (iterators != null && iterators.size() == 1 && StringUtil.isOneOf(iterators.get(0).getName(), "null", "_"))
return Collections.emptyList();
else
return iterators;
}
}