All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.boreal.model.functions.IntegraalInvokers Maven / Gradle / Ivy

The newest version!
package fr.boreal.model.functions;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import fr.boreal.model.logicalElements.api.Literal;
import fr.boreal.model.logicalElements.api.Term;
import fr.boreal.model.logicalElements.factory.api.TermFactory;
import org.apache.commons.lang3.tuple.Pair;

/**
 * Default invokers for InteGraal
 * @author Florent Tornil
 *
 */
public class IntegraalInvokers {

	private final TermFactory termFactory;
	private final Map strToStdInvoker;
	private final Map invokerMap;

	public enum StdInvoker {
		SUM,
		MIN,
		MAX,
		MINUS,
		PRODUCT,
		DIVIDE,
		AVERAGE,
		MEDIAN,
		IS_EVEN,
		IS_ODD,
		IS_GREATER_THAN,
		IS_GREATER_OR_EQUALS_TO,
		IS_SMALLER_THAN,
		IS_SMALLER_OR_EQUALS_TO,
		IS_LEXICOGRAPHICALLY_GREATER_THAN,
		IS_LEXICOGRAPHICALLY_GREATER_OR_EQUALS_TO,
		IS_LEXICOGRAPHICALLY_SMALLER_THAN,
		IS_LEXICOGRAPHICALLY_SMALLER_OR_EQUALS_TO,
		IS_PRIME,
		EQUALS,
		CONCAT,
		TO_LOWER_CASE,
		TO_UPPER_CASE,
		REPLACE,
		LENGTH,
		WEIGHTED_AVERAGE,
		WEIGHTED_MEDIAN,
		SET,
		IS_SUBSET,
		IS_STRICT_SUBSET,
		UNION,
		SIZE,
		INTERSECTION,
		CONTAINS,
		IS_EMPTY,
		IS_BLANK,
		IS_NUMERIC,
		PARSE_DOUBLE,
		PARSE_INT,
		DICT,
		MERGE_DICTS,
		DICT_KEYS,
		DICT_VALUES,
		GET,
		TUPLE,
		CONTAINS_KEY,
		CONTAINS_VALUE
	}

	/////////////////////////////////////////////////
	// Constructors
	/////////////////////////////////////////////////

	/**
	 * Default constructor
	 * @param tf a term factory
	 */
	public IntegraalInvokers(TermFactory tf) {
		this.termFactory = tf;

		strToStdInvoker = new HashMap<>();
		for (StdInvoker invoker : StdInvoker.values()) {
			strToStdInvoker.put(toCamelCase(invoker.name()), invoker);
		}

		invokerMap = new HashMap<>();
		for (Field field : getClass().getDeclaredFields()) {
			if (Invoker.class.isAssignableFrom(field.getType())) {
				StdInvoker enumConstant = strToStdInvoker.get(field.getName());
				if (enumConstant != null) {
					try {
						Invoker invoker = (Invoker) field.get(this);
						invokerMap.put(enumConstant, invoker);
					} catch (IllegalAccessException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
	}

	/**
	 * @param fct the name of the function
	 * @return the invoker corresponding to the given name
	 */
	public Invoker getInvoker(StdInvoker fct) {
		return invokerMap.get(fct);
	}

	/**
	 * @param fct the name of the function
	 * @return the invoker corresponding to the given name, null if none exists
	 */
	public Invoker getInvoker(String fct) {
		StdInvoker stdInvoker = strToStdInvoker.get(fct);
		return stdInvoker != null ? this.getInvoker(stdInvoker) : null;
	}

	/////////////////////////////////////////////////
	// Predefined Invokers
	/////////////////////////////////////////////////

	// Functions //

	private final Invoker sum = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			List numbers = parseNumbers(args);

			if (allIntegers(numbers)) {
				int sum = numbers.stream().mapToInt(n -> (Integer) n).sum();
				return Optional.ofNullable(termFactory.createOrGetLiteral(sum));
			} else {
				double sum = numbers.stream().mapToDouble(Number::doubleValue).sum();
				return Optional.ofNullable(termFactory.createOrGetLiteral(sum));
			}
		}
	};

	private final Invoker minus = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			List numbers = parseNumbers(args);

			if (numbers.isEmpty()) {
				return Optional.empty();
			}

			if (allIntegers(numbers)) {
				int result = (Integer) numbers.get(0);
				for (int i = 1; i < numbers.size(); i++) {
					result -= (Integer) numbers.get(i);
				}
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			} else {
				double result = numbers.get(0).doubleValue();
				for (int i = 1; i < numbers.size(); i++) {
					result -= numbers.get(i).doubleValue();
				}
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
		}
	};

	private final Invoker product = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			List numbers = parseNumbers(args);

			if (numbers.isEmpty()) {
				return Optional.empty();
			}

			if (allIntegers(numbers)) {
				int result = (Integer) numbers.get(0);
				for (int i = 1; i < numbers.size(); i++) {
					result *= (Integer) numbers.get(i);
				}
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
            double result = numbers.get(0).doubleValue();
            for (int i = 1; i < numbers.size(); i++) {
                result *= numbers.get(i).doubleValue();
            }
            return Optional.ofNullable(termFactory.createOrGetLiteral(result));
		}
	};
	
	private final Invoker divide = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			double result = Double.MAX_VALUE;
			for(Term t : args) {
				double t_i = Double.parseDouble(t.label());
				if(result == Double.MAX_VALUE) {
					result = t_i;
				} else {
					result /= t_i;
				}
			}
			return Optional.ofNullable(termFactory.createOrGetLiteral(result));
		}
	};

	private final Invoker max = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			List numbers = parseNumbers(args);

			if (allIntegers(numbers)) {
				int max = numbers.stream().mapToInt(n -> (Integer) n).max().orElse(Integer.MIN_VALUE);
				return Optional.ofNullable(termFactory.createOrGetLiteral(max));
			} else {
				double max = numbers.stream().mapToDouble(Number::doubleValue).max().orElse(Double.MIN_VALUE);
				return Optional.ofNullable(termFactory.createOrGetLiteral(max));
			}
		}
	};

	private final Invoker min = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			List numbers = parseNumbers(args);

			if (allIntegers(numbers)) {
				int min = numbers.stream().mapToInt(n -> (Integer) n).min().orElse(Integer.MAX_VALUE);
				return Optional.ofNullable(termFactory.createOrGetLiteral(min));
			} else {
				double min = numbers.stream().mapToDouble(Number::doubleValue).min().orElse(Double.MAX_VALUE);
				return Optional.ofNullable(termFactory.createOrGetLiteral(min));
			}
		}
	};

	private final Invoker average = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			try {
				return Optional.ofNullable(termFactory.createOrGetLiteral(
						Stream.of(args)
								.mapToDouble(t -> Double.parseDouble(t.label()))
								.average()
								.orElse(Double.NaN)
				));
			} catch (NumberFormatException e) {
				return Optional.empty();
			}
		}
	};

	private final Invoker median = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);

			List numbers = Stream.of(args)
					.map(t -> Double.parseDouble(t.label()))
					.sorted()
					.toList();

			int size = numbers.size();
			if (size == 0) {
				return Optional.empty();
			}

			double median;
			if (size % 2 == 0) {
				median = (numbers.get(size / 2 - 1) + numbers.get(size / 2)) / 2.0;
			} else {
				median = numbers.get(size / 2);
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(median));
		}
	};


	// Predicates //

	private final Invoker isEven = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory
					.createOrGetLiteral(args.length == 1 && (Integer.parseInt(args[0].label()) % 2 == 0)));
		}
	};

	private final Invoker isOdd = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory
					.createOrGetLiteral(args.length == 1 && (Integer.parseInt(args[0].label()) % 2 == 1)));
		}
	};

	private final Invoker isGreaterThan = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args.length == 2 && (Double.parseDouble(args[0].label()) > Double.parseDouble(args[1].label()))));
		}
	};

	private final Invoker isGreaterOrEqualsTo = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args.length == 2 && (Double.parseDouble(args[0].label()) >= Double.parseDouble(args[1].label()))));
		}
	};

	private final Invoker isSmallerThan = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args.length == 2 && (Double.parseDouble(args[0].label()) < Double.parseDouble(args[1].label()))));
		}
	};

	private final Invoker isSmallerOrEqualsTo = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args.length == 2 && (Double.parseDouble(args[0].label()) <= Double.parseDouble(args[1].label()))));
		}
	};

	private final Invoker isLexicographicallyGreaterThan = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length == 2) {
				String str1 = args[0].label();
				String str2 = args[1].label();
				boolean result = str1.compareTo(str2) > 0;
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
			return Optional.empty();
		}
	};

	private final Invoker isLexicographicallyGreaterOrEqualsTo = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length == 2) {
				String str1 = args[0].label();
				String str2 = args[1].label();
				boolean result = str1.compareTo(str2) >= 0;
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
			return Optional.empty();
		}
	};

	private final Invoker isLexicographicallySmallerThan = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length == 2) {
				String str1 = args[0].label();
				String str2 = args[1].label();
				boolean result = str1.compareTo(str2) < 0;
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
			return Optional.empty();
		}
	};


	private final Invoker isLexicographicallySmallerOrEqualsTo = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length == 2) {
				String str1 = args[0].label();
				String str2 = args[1].label();
				boolean result = str1.compareTo(str2) <= 0;
				return Optional.ofNullable(termFactory.createOrGetLiteral(result));
			}
			return Optional.empty();
		}
	};


	private final Invoker isPrime = new Invoker() {
		private boolean isPrime(int n) {
			if (n <= 1)
				return false;
			if (n <= 3)
				return true;
			if (n % 2 == 0 || n % 3 == 0)
				return false;
			for (int i = 5; i * i <= n; i += 6)
				if (n % i == 0 || n % (i + 2) == 0)
					return false;
			return true;
		}

		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory
					.createOrGetLiteral(args.length == 1 && (isPrime(Integer.parseInt(args[0].label())))));
		}
	};

	private final Invoker equals = new Invoker() {
		public boolean predicate(Term... args) {
			Term ref = args[0];
			return Arrays.stream(args).allMatch(t -> t.equals(ref));
		}

		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory
					.createOrGetLiteral(predicate(args)));
		}
	};

	private final Invoker concat = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length == 2 && args[0].isLiteral() && args[1].isLiteral()) {
				Object value1 = ((Literal) args[0]).value();
				Object value2 = ((Literal) args[1]).value();

				if (value1 instanceof List list1 && value2 instanceof List list2) {
					List concatenatedList = new ArrayList<>(list1);
					concatenatedList.addAll(list2);
					return Optional.ofNullable(termFactory.createOrGetLiteral(concatenatedList));
				}
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(
					Arrays.stream(args)
							.map(String::valueOf)
							.collect(Collectors.joining(""))
			));
		}
	};

	private final Invoker toLowerCase = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1) {
				return Optional.empty();
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(args[0].label().toLowerCase()));
		}
	};

	private final Invoker toUpperCase = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1) {
				return Optional.empty();
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(args[0].label().toUpperCase()));
		}
	};

	private final Invoker replace = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 3) {
				return Optional.empty();
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args[0].label().replace(args[1].label(), args[2].label())));
		}
	};

	private final Invoker length = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1) {
				return Optional.empty();
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(
					args[0].label().length()));
		}
	};

	private final Invoker weightedAverage = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);
			double sum = 0;
			double total = 0;

			if (areAllTuples(args)) {
				for (Term tuple : args) {
					List pair = (List) ((Literal) tuple).value();
					double value = Double.parseDouble(((Term) pair.get(0)).label());
					double weight = Double.parseDouble(((Term) pair.get(1)).label());
					sum += value * weight;
					total += Math.abs(weight);
				}
			} else {
				for (int i = 0; i < args.length - 1; i += 2) {
					sum += Double.parseDouble(args[i].label()) * Double.parseDouble(args[i + 1].label());
					total += Math.abs(Double.parseDouble(args[i + 1].label()));
				}
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(sum / total));
		}
	};

	private final Invoker weightedMedian = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			args = flattenIfCollection(args);

			List> valuesAndWeights = new ArrayList<>();

			if (areAllTuples(args)) {
				for (Term tuple : args) {
					List pair = (List) ((Literal) tuple).value();
					double value = Double.parseDouble(((Term) pair.get(0)).label());
					double weight = Double.parseDouble(((Term) pair.get(1)).label());
					valuesAndWeights.add(Pair.of(value, weight));
				}
			} else {
				if (args.length % 2 != 0) {
					return Optional.empty();
				}

				for (int i = 0; i < args.length; i += 2) {
					double value = Double.parseDouble(args[i].label());
					double weight = Double.parseDouble(args[i + 1].label());
					valuesAndWeights.add(Pair.of(value, weight));
				}
			}

			valuesAndWeights.sort(Map.Entry.comparingByKey());

			double totalWeight = valuesAndWeights.stream().mapToDouble(Pair::getValue).sum();
			double halfWeight = totalWeight / 2;

			double cumulativeWeight = 0;
			for (Pair pair : valuesAndWeights) {
				cumulativeWeight += pair.getValue();
				if (cumulativeWeight >= halfWeight) {
					return Optional.ofNullable(termFactory.createOrGetLiteral(pair.getKey()));
				}
			}

			return Optional.empty();
		}
	};

	// Functions about set of literals
	private final Invoker set  = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(new HashSet<>(Arrays.asList(args))));
		}
	};

	private final Invoker size = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral()) {
				throw new IllegalArgumentException(
						String.format("The computed function \"size\" can only received one argument that is Literal of " +
								"Collection or Map - provided arguments: %s", (Object) args));
			}

			if (((Literal) args[0]).value() instanceof Collection collection) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(collection.size()));
			} else if (((Literal) args[0]).value() instanceof Map map) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(map.size()));
			}

			throw new IllegalArgumentException(
					String.format("The computed function \"size\" can only be applied to a Literal that is a Collection " +
							"or a Map - provided arguments: %s", (Object) args));
		}
	};

	private final Invoker intersection = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !args[1].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Collection set1)
					|| !(((Literal) args[1]).value() instanceof Collection set2)) {
				return Optional.empty();
			}

			Collection smallerSet = set1.size() <= set2.size() ? set1 : set2;
			Collection largerSet = set1.size() > set2.size() ? set1 : set2;

			Set intersectionSet = smallerSet.stream()
					.filter(largerSet::contains)
					.collect(Collectors.toSet());

			return Optional.ofNullable(termFactory.createOrGetLiteral(intersectionSet));
		}
	};

	private final Invoker union = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !args[1].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Collection set1)
					|| !(((Literal) args[1]).value() instanceof Collection set2)) {
				return Optional.empty();
			}

			Set unionSet = new HashSet<>(set1);
			unionSet.addAll(set2);
			return Optional.ofNullable(termFactory.createOrGetLiteral(unionSet));
		}
	};

	private final Invoker isSubset = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !args[1].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Collection set1)
					|| !(((Literal) args[1]).value() instanceof Collection set2)) {
				return Optional.empty();
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(set2.containsAll(set1)));
		}
	};

	private final Invoker isStrictSubset = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !args[1].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Collection set1)
					|| !(((Literal) args[1]).value() instanceof Collection set2)) {
				return Optional.empty();
			}

			boolean isStrictSubset = set1.size() < set2.size() && set2.containsAll(set1);
			return Optional.ofNullable(termFactory.createOrGetLiteral(isStrictSubset));
		}
	};

	private final Invoker contains = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Collection collection)) {
				return Optional.empty();
			}
			return Optional.ofNullable(termFactory.createOrGetLiteral(collection.contains(args[1])));
		}
	};

	private final Invoker dict = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			Map dict = new HashMap<>();

			for (Term arg : args) {
				if (!arg.isLiteral() || !(((Literal) arg).value() instanceof List tuple) || tuple.size() != 2) {
					return Optional.empty();
				}

				Term key = (Term) tuple.get(0);
				Term value = (Term) tuple.get(1);

				dict.put(key, value);
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(dict));
		}
	};

	private final Invoker mergeDicts = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2
					|| !args[0].isLiteral()
					|| !args[1].isLiteral()
					|| !(((Literal) args[0]).value() instanceof Map dict1)
					|| !(((Literal) args[1]).value() instanceof Map dict2)) {
				return Optional.empty();
			}

			Map mergedDict = new HashMap<>((Map) dict1);
			mergedDict.putAll((Map) dict2);
			return Optional.ofNullable(termFactory.createOrGetLiteral(mergedDict));
		}
	};

	private final Invoker dictKeys = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral() || !(((Literal) args[0]).value() instanceof Map dict)) {
				return Optional.empty();
			}

			Set keys = dict.keySet();
			return Optional.ofNullable(termFactory.createOrGetLiteral(keys));
		}
	};

	private final Invoker dictValues = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral() || !(((Literal) args[0]).value() instanceof Map dict)) {
				return Optional.empty();
			}

			Collection values = dict.values();
			return Optional.ofNullable(termFactory.createOrGetLiteral(values));
		}
	};

	private final Invoker get = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2 || !args[0].isLiteral()) {
				return Optional.empty();
			}

			Literal container = (Literal) args[0];
			Term keyOrIndex = args[1];

			if (container.value() instanceof List tuple) {
				try {
					int index = Integer.parseInt(keyOrIndex.label());
					if (index < 0 || index >= tuple.size()) {
						return Optional.empty();
					}
					return Optional.ofNullable(termFactory.createOrGetLiteral(tuple.get(index)));
				} catch (NumberFormatException e) {
					return Optional.empty();
				}
			}

			if (container.value() instanceof Map dict) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(dict.get(keyOrIndex)));
			}

			return Optional.empty();
		}
	};

	private final Invoker tuple = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args == null || args.length == 0) {
				return Optional.empty();
			}
			List tuple = Arrays.asList(args);
			return Optional.ofNullable(termFactory.createOrGetLiteral(tuple));
		}
	};

	private final Invoker containsKey = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2 || !args[0].isLiteral() || !(((Literal) args[0]).value() instanceof Map dict)) {
				return Optional.empty();
			}

			Map dictionary = (Map) ((Literal) args[0]).value();
			Term key = args[1];

			return Optional.ofNullable(termFactory.createOrGetLiteral(dictionary.containsKey(key)));
		}
	};

	private final Invoker containsValue = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 2 || !args[0].isLiteral() || !(((Literal) args[0]).value() instanceof Map dict)) {
				return Optional.empty();
			}

			Map dictionary = (Map) ((Literal) args[0]).value();
			Term value = args[1];

			return Optional.ofNullable(termFactory.createOrGetLiteral(dictionary.containsValue(value)));
		}
	};

	private final Invoker isEmpty = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral()) {
				return Optional.empty();
			}

			Literal literal = (Literal) args[0];
			Object value = literal.value();

			if (value instanceof Collection collection) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(collection.isEmpty()));
			} else if (value instanceof String string) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(string.isEmpty()));
			}

			return Optional.empty();
		}
	};

	private final Invoker isBlank = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral()) {
				return Optional.empty();
			}

			Literal literal = (Literal) args[0];
			Object value = literal.value();

			if (value instanceof String string) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(string.isBlank()));
			}

			return Optional.empty();
		}
	};

	private final Invoker isNumeric = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			if (args.length != 1 || !args[0].isLiteral()) {
				return Optional.empty();
			}

			Literal literal = (Literal) args[0];
			Object value = literal.value();

			if (value instanceof Number) {
				return Optional.ofNullable(termFactory.createOrGetLiteral(true));
			} else if (value instanceof String string) {
				try {
					Double.parseDouble(string);
					return Optional.ofNullable(termFactory.createOrGetLiteral(true));
				} catch (NumberFormatException e) {
					return Optional.ofNullable(termFactory.createOrGetLiteral(false));
				}
			}

			return Optional.ofNullable(termFactory.createOrGetLiteral(false));
		}
	};

	private final Invoker parseDouble = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(Double.parseDouble(args[0].label())));
		}
	};

	private final Invoker parseInt = new Invoker() {
		@Override
		public Optional invoke(Term... args) {
			return Optional.ofNullable(termFactory.createOrGetLiteral(Integer.parseInt(args[0].label())));
		}
	};

	private String toCamelCase(String s) {
		String[] parts = s.toLowerCase().split("_");
		StringBuilder camelCaseString = new StringBuilder(parts[0]);
		for (int i = 1; i < parts.length; i++) {
			camelCaseString.append(parts[i].substring(0, 1).toUpperCase()).append(parts[i].substring(1));
		}
		return camelCaseString.toString();
	}

	private Term[] flattenIfCollection(Term... args) {
		if (args.length == 1 && args[0].isLiteral() && ((Literal) args[0]).value() instanceof Collection) {
			Collection collection = (Collection) ((Literal) args[0]).value();
			return collection.toArray(new Term[0]);
		}
		return args;
	}

	private boolean isTuple(Term term) {
		return term.isLiteral() && ((Literal) term).value() instanceof List list && list.size() == 2;
	}

	private boolean areAllTuples(Term... args) {
		return Stream.of(args).allMatch(this::isTuple);
	}

	private List parseNumbers(Term... args) {
		List numbers = new ArrayList<>();
		for (Term t : args) {
			try {
				numbers.add(Integer.parseInt(t.label()));
			} catch (NumberFormatException e) {
				numbers.add(Double.parseDouble(t.label()));
			}
		}
		return numbers;
	}

	private boolean allIntegers(List numbers) {
		return numbers.stream().allMatch(n -> n instanceof Integer);
	}

}