Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package at.ac.uibk.dps.cirrina.execution.object.expression;

import at.ac.uibk.dps.cirrina.execution.object.context.Extent;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
Expand Down Expand Up @@ -49,15 +60,18 @@ private static JexlEngine getJexlEngine() {
final Map<String, Object> namespaces = new HashMap<>();

namespaces.put("math", Math.class); // Enable math methods, e.g. math:sin(x), math:min(x, y), math:random()
namespaces.put("utility", Utility.class);
namespaces.put("std", Stdlib.class);

var features = new JexlFeatures().sideEffectGlobal(false).sideEffect(true);
var features = new JexlFeatures().sideEffectGlobal(true).sideEffect(true);

return new JexlBuilder()
.arithmetic(new CsmlArithmetic(true))
.features(features)
.cache(CACHE_SIZE)
.namespaces(namespaces)
.permissions(JexlPermissions.UNRESTRICTED)
.strict(true)
.silent(false)
.create();
}

Expand Down Expand Up @@ -96,11 +110,149 @@ public Object get(String key) {
}

@Override
public void set(String key, Object value) {}
public void set(String key, Object value) {
try {
extent.trySet(key, value);
} catch (IOException e) {
throw new NoSuchElementException(String.format("Variable not found: %s", key));
}
}

@Override
public boolean has(String key) {
return extent.resolve(key).isPresent();
}
}

/**
* CsmlArithmetic extends JexlArithmetic to provide custom operator overloading
* for collections, arrays, and maps in JEXL expressions.
*
* <p>Supports:
* <ul>
* <li>List + List / Array → List</li>
* <li>Set + Set / Array → LinkedHashSet</li>
* <li>Array + Array / List / Set → Object[]</li>
* <li>Map + Map → merged map (right overwrites left)</li>
* <li>Corresponding subtraction (-) removes elements or keys</li>
* </ul>
*
* <p>All operations are side-effect-free.
*/
private static class CsmlArithmetic extends JexlArithmetic {

/**
* Constructs a CsmlArithmetic instance with the specified strict mode.
*
* @param strict if true, the arithmetic engine runs in strict mode,
* where it throws exceptions for errors
*/
public CsmlArithmetic(boolean strict) {
super(strict);
}

/**
* Adds two objects together
*
* @param left the first operand
* @param right the second operand
* @return the result of the addition
*/
@Override
public Object add(Object left, Object right) {
// Left is a list: concatenate left and right, result is a List
if (left instanceof List<?>) {
return Stream.concat(toStream(left), toStream(right)).collect(Collectors.toList());
}

// Left is a set: concatenate left and right, result is a LinkedHashSet to preserve uniqueness
if (left instanceof Set<?>) {
return Stream.concat(toStream(left), toStream(right)).collect(
Collectors.toCollection(LinkedHashSet::new)
);
}

// Left is an array: concatenate left and right streams, result is an Object[]
if (left != null && left.getClass().isArray()) {
return Stream.concat(toStream(left), toStream(right)).toArray();
}

// Left and right are maps: merge entries, right-hand side overwrites left-hand side keys
if (left instanceof Map<?, ?> lm && right instanceof Map<?, ?> rm) {
return Stream.concat(lm.entrySet().stream(), rm.entrySet().stream()).collect(
Collectors.toMap(Entry::getKey, Entry::getValue, (oldV, newV) -> newV, HashMap::new)
);
}

// Delegate to default arithmetic
return super.add(left, right);
}

@Override
public Object subtract(Object left, Object right) {
// Left is List/Set/Array: remove elements present in right
if (isIterableLike(left) && isIterableLike(right)) {
Set<Object> rightSet = toStream(right).collect(Collectors.toSet());
Stream<Object> leftStream = toStream(left).filter(e -> !rightSet.contains(e));

if (left instanceof List<?>) {
return leftStream.collect(Collectors.toList());
}

if (left instanceof Set<?>) {
return leftStream.collect(Collectors.toCollection(LinkedHashSet::new));
}

if (left.getClass().isArray()) {
Class<?> componentType = left.getClass().getComponentType();
return leftStream.toArray(size -> (Object[]) Array.newInstance(componentType, size));
}
}

// Left is Map, right is Map or iterable of keys
if (left instanceof Map<?, ?> lm) {
Set<Object> keysToRemove = Stream.of(right)
.flatMap(r -> {
if (r instanceof Map<?, ?> rm) {
return rm.keySet().stream();
} else if (isIterableLike(r)) {
return toStream(r);
} else {
return Stream.of(r);
}
})
.collect(Collectors.toSet());

return lm
.entrySet()
.stream()
.filter(e -> !keysToRemove.contains(e.getKey()))
.collect(
Collectors.toMap(
Entry::getKey,
Entry::getValue,
(a, b) -> b,
() -> new HashMap<>(lm.size())
)
);
}

// Delegate to default arithmetic
return super.subtract(left, right);
}

private static boolean isIterableLike(Object o) {
return o instanceof Collection<?> || (o != null && o.getClass().isArray());
}

private static Stream<Object> toStream(Object o) {
if (o instanceof Collection<?> c) {
return c.stream().map(x -> x);
}
if (o != null && o.getClass().isArray()) {
return IntStream.range(0, Array.getLength(o)).mapToObj(i -> Array.get(o, i));
}
return Stream.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package at.ac.uibk.dps.cirrina.execution.`object`.expression

import java.util.*

class Stdlib {
companion object {
@JvmStatic
fun genRandPayload(sizes: IntArray): ByteArray {
val rand = Random()

val randomIndex = rand.nextInt(sizes.size)
val selectedSize = sizes[randomIndex]

return ByteArray(selectedSize)
}
}
}

This file was deleted.

Loading