Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.carml.engine.function.Functions Maven / Gradle / Ivy
package io.carml.engine.function;
import io.carml.engine.RmlMapperException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("java:S1135")
public class Functions {
private static final Logger LOG = LoggerFactory.getLogger(Functions.class);
private static final ValueFactory VF = SimpleValueFactory.getInstance();
private final Map fns = new LinkedHashMap<>();
public Optional getFunction(IRI iri) {
return Optional.ofNullable(fns.get(iri));
}
public void addFunctions(Object... functions) {
for (Object fn : functions) {
Arrays.stream(fn.getClass()
.getMethods())
.map(method -> createFunctionExecutor(fn, method))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(function -> fns.put(function.getIri(), function));
}
}
private Optional createFunctionExecutor(Object obj, Method method) {
FnoFunction function = method.getAnnotation(FnoFunction.class);
if (function == null) {
return Optional.empty();
}
var iri = VF.createIRI(function.value());
List parameterExtractors = Arrays.stream(method.getParameters())
.map(this::createParameterExtractor)
.collect(Collectors.toList());
LOG.debug("Creating executable FnO function {}", function);
return Optional.of(new ExecuteFunction() {
@Override
public Object execute(Model model, Resource subject, UnaryOperator returnValueAdapter) {
List arguments = parameterExtractors.stream()
.map(extractor -> extractor.extract(model, subject))
.collect(Collectors.toList());
try {
if (LOG.isTraceEnabled()) {
LOG.trace("Executing function {} with arguments {}", method.getName(), arguments);
}
Object returnValue = method.invoke(obj, arguments.toArray());
if (returnValue == null) {
return null;
}
return returnValueAdapter.apply(returnValue);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
throw new RmlMapperException("error executing function", exception);
}
}
@Override
public IRI getIri() {
return iri;
}
});
}
private ExtractParameter createParameterExtractor(Parameter parameter) {
FnoParam param = parameter.getAnnotation(FnoParam.class);
if (param == null) {
throw new RmlMapperException(String.format("no @%s annotation present on parameter", FnoParam.class.getName()));
}
var iri = VF.createIRI(param.value());
Type type = parameter.getType();
Function, Object> adapter;
if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {
adapter = values -> singleValueExtraction(values, this::literalToInt);
} else if (type.equals(String.class)) {
adapter = values -> singleValueExtraction(values, this::literalToString);
} else if (type.equals(Double.TYPE) || type.equals(Double.class)) {
adapter = values -> singleValueExtraction(values, this::literalToDouble);
} else if (type.equals(Float.TYPE) || type.equals(Float.class)) {
adapter = values -> singleValueExtraction(values, this::literalToFloat);
} else if (type.equals(Long.TYPE) || type.equals(Long.class)) {
adapter = values -> singleValueExtraction(values, this::literalToLong);
} else if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {
adapter = values -> singleValueExtraction(values, this::literalToBoolean);
} else if (Collection.class.isAssignableFrom(parameter.getType())) {
// TODO: Currently only collections with string parameter type supported.
adapter = this::collectionValueExtraction;
} else {
throw new RmlMapperException(String.format("parameter type [%s] not (yet) supported", type));
}
return (model, subject) -> {
var paramValues = model.filter(subject, iri, null);
List values = paramValues.stream()
.map(Statement::getObject)
.collect(Collectors.toUnmodifiableList());
return adapter.apply(values);
};
}
private Object singleValueExtraction(List values, Function literalProcessor) {
if (values == null || values.isEmpty()) {
// Return null for empty function parameter
return null;
}
expectSingleValue(values);
return literalProcessor.apply(values.get(0));
}
private Object collectionValueExtraction(List values) {
if (values == null || values.isEmpty()) {
// Return null for empty function parameter
return null;
}
return values.stream()
.map(Value::stringValue)
.collect(Collectors.toUnmodifiableList());
}
public int size() {
return fns.size();
}
private void expectSingleValue(List values) {
if (values.size() > 1) {
throw new IllegalArgumentException(
String.format("value [%s] has more than one value, which is not expected.", values));
}
}
private String literalToString(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(
String.format("value [%s] was not a literal, which is expected for a parameter of type String.", value));
}
var literal = (Literal) value;
return literal.stringValue();
}
private int literalToInt(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(String
.format("value [%s] was not a literal, which is expected for a parameter of type int or Integer.", value));
}
var literal = (Literal) value;
return literal.intValue();
}
private double literalToDouble(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(String
.format("value [%s] was not a literal, which is expected for a parameter of type double or Double.", value));
}
var literal = (Literal) value;
return literal.doubleValue();
}
private float literalToFloat(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(String
.format("value [%s] was not a literal, which is expected for a parameter of type float or Float.", value));
}
var literal = (Literal) value;
return literal.floatValue();
}
private long literalToLong(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(String
.format("value [%s] was not a literal, which is expected for a parameter of type long or Long.", value));
}
var literal = (Literal) value;
return literal.longValue();
}
private boolean literalToBoolean(Value value) {
if (!(value instanceof Literal)) {
throw new IllegalArgumentException(String.format(
"value [%s] was not a literal, which is expected for a parameter of type boolean or Boolean.", value));
}
var literal = (Literal) value;
return literal.booleanValue();
}
}