/*
* Copyright 2015 by Thomas Lottermann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package notaql.evaluation;
import notaql.datamodel.Value;
import notaql.datamodel.fixation.Fixation;
import notaql.model.EvaluationException;
import notaql.model.function.FunctionProvider;
import notaql.model.function.SimpleFunction;
import notaql.model.vdata.GenericFunctionVData;
import notaql.model.vdata.VData;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* This evaluates generic functions. Right now this just supports simple functions
* (because they are currently the only pluggable functions).
*
* TODO: Make sure to translate all other functions to reduce this weird inconsistent state
*/
public class GenericFunctionVDataEvaluator implements Evaluator {
private Map<String, Method> simpleFunctions = new HashMap<>();
/**
* Grabs all functions that are available
*/
public GenericFunctionVDataEvaluator() {
final ServiceLoader<FunctionProvider> functionProviderLoader = ServiceLoader.load(FunctionProvider.class);
// extract all simpleFunctions from the classes
for (FunctionProvider functionProvider : functionProviderLoader) {
for (Method method : functionProvider.getClass().getMethods()) {
final SimpleFunction annotation = method.getAnnotation(SimpleFunction.class);
if(annotation == null)
continue;
// make sure that the return type is okay
if(!Value.class.isAssignableFrom(method.getReturnType())) {
throw new AssertionError("Method " + annotation.name() + " has invalid return type");
}
simpleFunctions.put(annotation.name(), method);
}
}
}
/**
* Evaluates simple functions in the way, that all arguments are evaluated. In case one argument is ambiguous,
* we simply evaluate the function a couple of times.
* If more than one is ambiguous we throw an exception. A cross product would be possible, but as of now we did
* not encounter a real usecase for that.
*
* @param vData
* @param fixation
* @return
*/
@Override
public List<ValueEvaluationResult> evaluate(VData vData, Fixation fixation) {
assert vData instanceof GenericFunctionVData;
final GenericFunctionVData functionVData = (GenericFunctionVData) vData;
final Method method = simpleFunctions.get(functionVData.getName());
if(method == null)
throw new EvaluationException(functionVData.getName() + " is an unknown function.");
final VData[] args = functionVData.getArgs();
final List<List<ValueEvaluationResult>> results = new LinkedList<>();
if(args.length < method.getParameterCount())
throw new EvaluationException(functionVData.getName() + " was provided with too few arguments.");
Fixation lastFixation = fixation;
// evaluate each argument
for (VData arg : args) {
final List<ValueEvaluationResult> evaluate = EvaluatorService.getInstance().evaluate(arg, lastFixation);
if(evaluate.size() > 0)
lastFixation = evaluate.get(evaluate.size() - 1).getFixation();
results.add(evaluate);
}
// make sure that at most one argument is ambigous.
int ambigous = -1;
int i = 0;
for (List<ValueEvaluationResult> result : results) {
if(result.size() > 1) {
if (ambigous > -1)
throw new EvaluationException(functionVData.getName() + ": Two arguments were ambigous. This is not (yet) supported.");
ambigous = i;
}
i++;
}
final Iterator<List<ValueEvaluationResult>> iterator = results.iterator();
// check if types match
// TODO: add support for ... parameters
for (Class<?> aClass : method.getParameterTypes()) {
// guaranteed before
assert iterator.hasNext();
final List<ValueEvaluationResult> result = iterator.next();
// check if types match
for (ValueEvaluationResult evaluationResult : result) {
if(!aClass.isAssignableFrom(evaluationResult.getValue().getClass()))
throw new EvaluationException(functionVData.getName() + " encountered wrong types");
}
}
Object[] params = new Object[results.size()];
// copy params
int j = 0;
for (List<ValueEvaluationResult> result : results) {
if(j != ambigous) {
params[j] = result.get(0).getValue();
}
j++;
}
final List<ValueEvaluationResult> returns = new LinkedList<>();
// invoke the function and store the results in results
try {
if(ambigous > -1) {
for (ValueEvaluationResult evaluationResult : results.get(ambigous)) {
params[ambigous] = evaluationResult.getValue();
final Object invoke = method.invoke(null, params);
assert invoke instanceof Value;
returns.add(new ValueEvaluationResult((Value)invoke, lastFixation));
}
} else {
final Object invoke = method.invoke(null, params);
assert invoke instanceof Value;
returns.add(new ValueEvaluationResult((Value)invoke, lastFixation));
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new EvaluationException("Method " + functionVData.getName() + " could not be invoked.", e);
}
return returns;
}
@Override
public boolean canReduce(VData vData) {
return false;
}
@Override
public List<Class<? extends VData>> getProcessedClasses() {
return Arrays.asList(GenericFunctionVData.class);
}
}