diff --git a/lang/expression-parser/model/pom.xml b/lang/expression-parser/model/pom.xml
new file mode 100644
index 00000000..d17840fe
--- /dev/null
+++ b/lang/expression-parser/model/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ tools.vitruv
+ tools.vitruv.neojoin.expression_parser
+ 0.1.0-SNAPSHOT
+
+
+ tools.vitruv.neojoin.expression_parser.model
+
+ Expression Parser Model
+
+
+
+
+ org.eclipse.xtext
+ org.eclipse.xtext.xbase
+
+
+ org.projectlombok
+ lombok
+
+
+ org.jspecify
+ jspecify
+
+
+
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureCall.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureCall.java
new file mode 100644
index 00000000..d636b254
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureCall.java
@@ -0,0 +1,49 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A FeatureCall represents a feature (e.g. a variable), stores information about the type and is
+ * the first operation in a reference chain.
+ *
+ *
An example expression may look like
+ *
+ *
+ * {@code someResult = car.axis.flatMap(a -> a.wheels).toList()}
+ *
+ *
+ * Here, {@code car} is a FeatureCall
+ *
+ * A FeatureCall is also the first operation in a nested expression:
+ *
+ *
+ * {@code someResult = car.axis.flatMap(oneAxis -> oneAxis.wheels).toList()}
+ *
+ *
+ * Here, {@code oneAxis} is also a FeatureCall
+ */
+@Data
+@RequiredArgsConstructor
+public class FeatureCall implements ReferenceOperator {
+ @Nullable final String identifier;
+ @Nullable final String simpleName;
+
+ @Nullable ReferenceOperator followingOperator;
+
+ public static FeatureCall empty() {
+ return new FeatureCall(null, null);
+ }
+
+ @Override
+ public String toString() {
+ final String stringRepresentation = "FeatureCall(" + simpleName + ")";
+ if (followingOperator == null) {
+ return stringRepresentation;
+ }
+
+ return stringRepresentation + "->" + followingOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureInformation.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureInformation.java
new file mode 100644
index 00000000..ca0f1554
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FeatureInformation.java
@@ -0,0 +1,10 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Value;
+
+@Value
+public class FeatureInformation {
+ String featureName;
+ String featureClassSimpleName;
+ String featureClassIdentifier;
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FindAny.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FindAny.java
new file mode 100644
index 00000000..c0c8a92f
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FindAny.java
@@ -0,0 +1,27 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * FindAny selects any element from a collection of elements. There are no guarantees which element
+ * will be selected
+ *
+ * Example expressions may look like
+ *
+ *
+ * {@code
+ * someResult = car.axis.findFirst()
+ * someResult = car.axis.findLast()
+ * }
+ *
+ *
+ * Here, {@code X.findFirst()} and {@code X.findLast()} are FindAny operations
+ */
+@Data
+@RequiredArgsConstructor
+public class FindAny implements ReferenceOperator {
+ @Nullable ReferenceOperator followingOperator;
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FlatMap.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FlatMap.java
new file mode 100644
index 00000000..2439c39a
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/FlatMap.java
@@ -0,0 +1,37 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A FlatMap represents mapping a parent object to some children along a one-to-many
+ * reference and flattening the result. It contains information about the reference and the child type
+ *
+ * An example expression may look like
+ *
+ *
+ * {@code someResult = car.axis.flatMap(a -> a.wheels).toList()}
+ *
+ *
+ * Here, {@code X.flatMap(a -> a.wheels)} is a FlatMap
+ */
+@Data
+@RequiredArgsConstructor
+public class FlatMap implements ReferenceOperator {
+ @NonNull final FeatureInformation featureInformation;
+
+ @Nullable ReferenceOperator followingOperator;
+
+ @Override
+ public String toString() {
+ final String stringRepresentation = "FlatMap(" + featureInformation.getFeatureName() + ")";
+ if (followingOperator == null) {
+ return stringRepresentation;
+ }
+
+ return stringRepresentation + "->" + followingOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/Map.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/Map.java
new file mode 100644
index 00000000..69e8362c
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/Map.java
@@ -0,0 +1,37 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A Map represents mapping a parent object to some child along a one-to-one reference. It
+ * contains information about the reference and the child type
+ *
+ * An example expression may look like
+ *
+ *
+ * {@code someResult = car.axis.map(a -> a.axisInformation).toList()}
+ *
+ *
+ * Here, {@code X.map(a -> a.axisInformation)} is a Map
+ */
+@Data
+@RequiredArgsConstructor
+public class Map implements ReferenceOperator {
+ @NonNull final FeatureInformation featureInformation;
+
+ @Nullable ReferenceOperator followingOperator;
+
+ @Override
+ public String toString() {
+ final String stringRepresentation = "Map(" + featureInformation.getFeatureName() + ")";
+ if (followingOperator == null) {
+ return stringRepresentation;
+ }
+
+ return stringRepresentation + "->" + followingOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/MemberFeatureCall.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/MemberFeatureCall.java
new file mode 100644
index 00000000..512ddc4d
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/MemberFeatureCall.java
@@ -0,0 +1,38 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A MemberFeatureCall represents a field/reference access of a parent class. It contains
+ * information about the type and reference name. The reference can have an upper and/or lower bound
+ *
+ * An example expression may look like
+ *
+ *
+ * {@code someResult = car.axis.flatMap(a -> a.wheels).toList()}
+ *
+ *
+ * Here, {@code X.axis} is a MemberFeatureCall
+ */
+@Data
+@RequiredArgsConstructor
+public class MemberFeatureCall implements ReferenceOperator {
+ @NonNull final FeatureInformation featureInformation;
+ final boolean isCollection;
+
+ @Nullable ReferenceOperator followingOperator;
+
+ @Override
+ public String toString() {
+ final String stringRepresentation = "MemberFeatureCall(" + featureInformation.getFeatureName() + ")";
+ if (followingOperator == null) {
+ return stringRepresentation;
+ }
+
+ return stringRepresentation + "->" + followingOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceFilter.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceFilter.java
new file mode 100644
index 00000000..8f6e16ae
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceFilter.java
@@ -0,0 +1,49 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ComparisonOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ConstantValue;
+
+/**
+ * A ReferenceFilter represents a predicate for the previous ReferenceOperator. Only predicates that
+ * compare a feature to some constant value are supported
+ *
+ * An example expression may look like
+ *
+ *
+ * {@code someResult = car.axis.filter(a -> a.position == "front").toList()}
+ *
+ *
+ * Here, {@code X.filter(a -> a.position == "front")} is a ReferenceFilter
+ */
+@Data
+@RequiredArgsConstructor
+public class ReferenceFilter implements ReferenceOperator {
+ @NonNull final String feature;
+ @NonNull final ComparisonOperator operator;
+ @NonNull final ConstantValue constantValue;
+
+ @Nullable ReferenceOperator followingOperator;
+
+ @Override
+ public String toString() {
+ final String stringRepresentation =
+ "ReferenceFilter("
+ + feature
+ + " "
+ + operator.getRepresentation()
+ + " "
+ + constantValue
+ + ")";
+ if (followingOperator == null) {
+ return stringRepresentation;
+ }
+
+ return stringRepresentation + "->" + followingOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceOperator.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceOperator.java
new file mode 100644
index 00000000..80a4d22c
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/ReferenceOperator.java
@@ -0,0 +1,25 @@
+package tools.vitruv.neojoin.expression_parser.model;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A ReferenceOperator is a (partial) parsed expression that can be used for model transformations.
+ * It contains the required properties (e.g. type, field names) for the following steps and possibly
+ * the following ReferenceOperator-chain
+ */
+public interface ReferenceOperator {
+ @Nullable ReferenceOperator getFollowingOperator();
+
+ void setFollowingOperator(ReferenceOperator followingOperator);
+
+ /** Returns the last ReferenceOperator in this chain */
+ @NonNull
+ default ReferenceOperator getLastOperatorInChain() {
+ ReferenceOperator lastOperator = this;
+ while (lastOperator.getFollowingOperator() != null) {
+ lastOperator = lastOperator.getFollowingOperator();
+ }
+ return lastOperator;
+ }
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ComparisonOperator.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ComparisonOperator.java
new file mode 100644
index 00000000..4941cf47
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ComparisonOperator.java
@@ -0,0 +1,17 @@
+package tools.vitruv.neojoin.expression_parser.model.predicate_expression;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum ComparisonOperator {
+ Equals("=="),
+ NotEquals("!="),
+ LessThan("<"),
+ LessEquals("<="),
+ GreaterThan(">"),
+ GreaterEquals(">=");
+
+ final String representation;
+}
diff --git a/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ConstantValue.java b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ConstantValue.java
new file mode 100644
index 00000000..5df71b1f
--- /dev/null
+++ b/lang/expression-parser/model/src/main/java/tools/vitruv/neojoin/expression_parser/model/predicate_expression/ConstantValue.java
@@ -0,0 +1,29 @@
+package tools.vitruv.neojoin.expression_parser.model.predicate_expression;
+
+import lombok.Value;
+
+@Value
+public class ConstantValue {
+ String value;
+ boolean isString;
+
+ public static ConstantValue fromString(String value) {
+ return new ConstantValue(value, true);
+ }
+
+ public static ConstantValue fromBoolean(boolean isTrue) {
+ return ConstantValue.of(Boolean.toString(isTrue));
+ }
+
+ public static ConstantValue of(String value) {
+ return new ConstantValue(value, false);
+ }
+
+ @Override
+ public String toString() {
+ if (isString) {
+ return "\"" + value + "\"";
+ }
+ return value;
+ }
+}
diff --git a/lang/expression-parser/parser/pom.xml b/lang/expression-parser/parser/pom.xml
new file mode 100644
index 00000000..58f62472
--- /dev/null
+++ b/lang/expression-parser/parser/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ tools.vitruv
+ tools.vitruv.neojoin.expression_parser
+ 0.1.0-SNAPSHOT
+
+
+ tools.vitruv.neojoin.expression_parser.parser
+
+ Expression Parser Logic
+
+
+
+
+ org.eclipse.xtext
+ org.eclipse.xtext.xbase
+
+
+ org.projectlombok
+ lombok
+
+
+ org.jspecify
+ jspecify
+
+
+ ${project.groupId}
+ tools.vitruv.neojoin.expression_parser.model
+ ${project.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/exception/UnsupportedReferenceExpressionException.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/exception/UnsupportedReferenceExpressionException.java
new file mode 100644
index 00000000..cc30190f
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/exception/UnsupportedReferenceExpressionException.java
@@ -0,0 +1,18 @@
+package tools.vitruv.neojoin.expression_parser.parser.exception;
+
+import org.eclipse.xtext.xbase.XExpression;
+
+public class UnsupportedReferenceExpressionException extends Exception {
+ public static UnsupportedReferenceExpressionException fromExpression(XExpression expression) {
+ return new UnsupportedReferenceExpressionException(
+ String.format("The expression %s is not supported", expression));
+ }
+
+ public UnsupportedReferenceExpressionException(String message) {
+ super(message);
+ }
+
+ public UnsupportedReferenceExpressionException(String message, XExpression expression) {
+ super(String.format("%s. The expression was %s", message, expression));
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/PatternMatchingStrategy.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/PatternMatchingStrategy.java
new file mode 100644
index 00000000..1beec37a
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/PatternMatchingStrategy.java
@@ -0,0 +1,12 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy;
+
+import org.eclipse.xtext.xbase.XExpression;
+import org.jspecify.annotations.NonNull;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+
+public interface PatternMatchingStrategy {
+ @NonNull ReferenceOperator parseReferenceOperator(@NonNull XExpression expression)
+ throws UnsupportedReferenceExpressionException;
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/ManualPatternMatchingStrategy.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/ManualPatternMatchingStrategy.java
new file mode 100644
index 00000000..63bebced
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/ManualPatternMatchingStrategy.java
@@ -0,0 +1,43 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching;
+
+import org.eclipse.xtext.xbase.XExpression;
+import org.jspecify.annotations.NonNull;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.CollectReferencesParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.FeatureCallParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.FilterParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.FindAnyParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.FlatMapParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.MapParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.MemberFeatureCallParser;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers.ReferenceOperatorParser;
+
+import java.util.List;
+
+public class ManualPatternMatchingStrategy implements PatternMatchingStrategy {
+ private static final List PARSERS =
+ List.of(
+ new FeatureCallParser(),
+ new MemberFeatureCallParser(),
+ new FilterParser(),
+ new CollectReferencesParser(),
+ new FlatMapParser(),
+ new MapParser(),
+ new FindAnyParser());
+
+ @Override
+ public @NonNull ReferenceOperator parseReferenceOperator(@NonNull XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ for (var parser : PARSERS) {
+ final var result = parser.parse(this, expression);
+ if (result.isPresent()) {
+ return result.get();
+ }
+ }
+
+ throw UnsupportedReferenceExpressionException.fromExpression(expression);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParser.java
new file mode 100644
index 00000000..d61aebaf
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParser.java
@@ -0,0 +1,38 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XExpression;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+
+import java.util.Optional;
+import java.util.Set;
+
+public class CollectReferencesParser implements ReferenceOperatorParser {
+ private static final String TO_LIST_OPERATION_SIMPLE_NAME = "toList";
+ private static final Set COLLECT_OPERATIONS_SIMPLE_NAMES =
+ Set.of(TO_LIST_OPERATION_SIMPLE_NAME);
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ final Optional jvmOperationSimpleName =
+ CastingUtils.asMemberFeatureCall(expression)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmIdentifiableElement::getSimpleName);
+ if (jvmOperationSimpleName.isEmpty()) {
+ return Optional.empty();
+ }
+
+ if (!COLLECT_OPERATIONS_SIMPLE_NAMES.contains(jvmOperationSimpleName.get())) {
+ return Optional.empty();
+ }
+
+ return parseAndAppendFollowingExpressionOperators(strategy, expression, null);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParser.java
new file mode 100644
index 00000000..efb9e877
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParser.java
@@ -0,0 +1,31 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XExpression;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+
+import java.util.Optional;
+
+public class FeatureCallParser implements ReferenceOperatorParser {
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression) {
+ return CastingUtils.asFeatureCall(expression)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmFormalParameter)
+ .map(
+ parameter -> {
+ if (parameter.getParameterType() == null) {
+ return FeatureCall.empty();
+ }
+
+ JvmType parameterType = parameter.getParameterType().getType();
+ return new FeatureCall(
+ parameterType.getIdentifier(), parameterType.getSimpleName());
+ });
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParser.java
new file mode 100644
index 00000000..08443884
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParser.java
@@ -0,0 +1,64 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceFilter;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.BlockExpressionUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.JvmMemberCallUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.PredicateExpressionUtils;
+
+import java.util.Optional;
+
+public class FilterParser implements ReferenceOperatorParser {
+ private static final String FILTER_OPERATION_SIMPLE_NAME = "filter";
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ final Optional binaryOperation =
+ Optional.of(expression)
+ .flatMap(CastingUtils::asMemberFeatureCall)
+ .filter(FilterParser::isFilterOperation)
+ .filter(JvmMemberCallUtils::hasExactlyOneMemberCallArgument)
+ .flatMap(JvmMemberCallUtils::getFirstArgument)
+ .flatMap(CastingUtils::asClosure)
+ .map(XClosure::getExpression)
+ .flatMap(CastingUtils::asBlockExpression)
+ .filter(BlockExpressionUtils::hasExactlyOneExpression)
+ .flatMap(BlockExpressionUtils::getFirstExpression)
+ .flatMap(CastingUtils::asBinaryOperation);
+ if (binaryOperation.isEmpty()) {
+ return Optional.empty();
+ }
+
+ // Try to parse filter expression (throws if not possible)
+ final PredicateExpressionUtils.ConstantPredicate constantFilterPredicate =
+ PredicateExpressionUtils.extractConstantPredicate(binaryOperation.get());
+
+ return parseAndAppendFollowingExpressionOperators(
+ strategy,
+ expression,
+ new ReferenceFilter(
+ constantFilterPredicate.getFeature(),
+ constantFilterPredicate.getOperator(),
+ constantFilterPredicate.getConstantValue()));
+ }
+
+ private static boolean isFilterOperation(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmIdentifiableElement::getSimpleName)
+ .map(FILTER_OPERATION_SIMPLE_NAME::equals)
+ .orElse(false);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParser.java
new file mode 100644
index 00000000..6098de11
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParser.java
@@ -0,0 +1,55 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.FindAny;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.BlockExpressionUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.JvmMemberCallUtils;
+
+import java.util.Optional;
+import java.util.Set;
+
+public class FindAnyParser implements ReferenceOperatorParser {
+ private static final String FIND_FIRST_OPERATION_SIMPLE_NAME = "findFirst";
+ private static final String FIND_LAST_OPERATION_SIMPLE_NAME = "findLast";
+ private static final Set FIND_ANY_OPERATION_SIMPLE_NAMES =
+ Set.of(FIND_FIRST_OPERATION_SIMPLE_NAME, FIND_LAST_OPERATION_SIMPLE_NAME);
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ boolean isFindAnyWithoutArguments =
+ Optional.ofNullable(expression)
+ .flatMap(CastingUtils::asMemberFeatureCall)
+ .filter(FindAnyParser::isFindAnyOperation)
+ .filter(JvmMemberCallUtils::hasExactlyOneMemberCallArgument)
+ .flatMap(JvmMemberCallUtils::getFirstArgument)
+ .flatMap(CastingUtils::asClosure)
+ .map(XClosure::getExpression)
+ .flatMap(CastingUtils::asBlockExpression)
+ .filter(BlockExpressionUtils::hasNoExpressions)
+ .isPresent();
+ if (!isFindAnyWithoutArguments) {
+ return Optional.empty();
+ }
+
+ return parseAndAppendFollowingExpressionOperators(strategy, expression, new FindAny());
+ }
+
+ private static boolean isFindAnyOperation(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmOperation::getSimpleName)
+ .map(FIND_ANY_OPERATION_SIMPLE_NAMES::contains)
+ .orElse(false);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParser.java
new file mode 100644
index 00000000..386285ea
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParser.java
@@ -0,0 +1,116 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.FlatMap;
+import tools.vitruv.neojoin.expression_parser.model.Map;
+import tools.vitruv.neojoin.expression_parser.model.MemberFeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceFilter;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.BlockExpressionUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.JvmMemberCallUtils;
+
+import java.util.Optional;
+
+public class FlatMapParser implements ReferenceOperatorParser {
+ private static final String FLAT_MAP_OPERATION_SIMPLE_NAME = "flatMap";
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ // Check that expression is flatMap and get single argument
+ final Optional flatMapArgument =
+ CastingUtils.asMemberFeatureCall(expression)
+ .filter(FlatMapParser::isFlatMapOperation)
+ .filter(JvmMemberCallUtils::hasExactlyOneMemberCallArgument)
+ .flatMap(JvmMemberCallUtils::getFirstArgument);
+ if (flatMapArgument.isEmpty()) {
+ return Optional.empty();
+ }
+
+ // Check that expression can be parsed
+ final Optional flatMapArgumentExpression =
+ flatMapArgument
+ .flatMap(CastingUtils::asClosure)
+ .map(XClosure::getExpression)
+ .flatMap(CastingUtils::asBlockExpression)
+ .filter(BlockExpressionUtils::hasExactlyOneExpression)
+ .flatMap(BlockExpressionUtils::getFirstExpression);
+ if (flatMapArgumentExpression.isEmpty()) {
+ return Optional.empty();
+ }
+
+ final ReferenceOperator flatMapArgumentOperator =
+ strategy.parseReferenceOperator(flatMapArgumentExpression.get());
+ if (!(flatMapArgumentOperator instanceof FeatureCall)) {
+ throw new UnsupportedReferenceExpressionException(
+ "The first element of a flatMap expression must be a feature call",
+ flatMapArgumentExpression.get());
+ }
+
+ ReferenceOperator currentFlatMapArgumentOperator =
+ flatMapArgumentOperator.getFollowingOperator();
+ if (currentFlatMapArgumentOperator == null) {
+ throw new UnsupportedReferenceExpressionException(
+ "The flatMap expression must contain more than a feature call",
+ flatMapArgumentExpression.get());
+ }
+
+ final ReferenceOperator operatorHead =
+ extractFlatMapArgumentOperator(
+ currentFlatMapArgumentOperator, flatMapArgumentExpression.get());
+ currentFlatMapArgumentOperator = currentFlatMapArgumentOperator.getFollowingOperator();
+
+ ReferenceOperator lastOperator = operatorHead;
+ while (currentFlatMapArgumentOperator != null) {
+ final ReferenceOperator nextOperator =
+ extractFlatMapArgumentOperator(
+ currentFlatMapArgumentOperator, flatMapArgumentExpression.get());
+
+ lastOperator.setFollowingOperator(nextOperator);
+ lastOperator = nextOperator;
+ currentFlatMapArgumentOperator = currentFlatMapArgumentOperator.getFollowingOperator();
+ }
+
+ return parseAndAppendFollowingExpressionOperators(strategy, expression, operatorHead);
+ }
+
+ private static ReferenceOperator extractFlatMapArgumentOperator(
+ ReferenceOperator flatMapArgumentOperator, XExpression flatMapArgumentExpression)
+ throws UnsupportedReferenceExpressionException {
+ if (flatMapArgumentOperator instanceof MemberFeatureCall memberFeatureCall
+ && memberFeatureCall.isCollection()) {
+ return new FlatMap(memberFeatureCall.getFeatureInformation());
+ } else if (flatMapArgumentOperator instanceof MemberFeatureCall memberFeatureCall
+ && !memberFeatureCall.isCollection()) {
+ return new Map(memberFeatureCall.getFeatureInformation());
+ } else if (flatMapArgumentOperator instanceof Map mapCall) {
+ return new Map(mapCall.getFeatureInformation());
+ } else if (flatMapArgumentOperator instanceof FlatMap flatMapCall) {
+ return new FlatMap(flatMapCall.getFeatureInformation());
+ } else if (flatMapArgumentOperator instanceof ReferenceFilter filter) {
+ return new ReferenceFilter(
+ filter.getFeature(), filter.getOperator(), filter.getConstantValue());
+ }
+
+ throw new UnsupportedReferenceExpressionException(
+ "The flatMap expression is not supported", flatMapArgumentExpression);
+ }
+
+ private static boolean isFlatMapOperation(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmIdentifiableElement::getSimpleName)
+ .map(FLAT_MAP_OPERATION_SIMPLE_NAME::equals)
+ .orElse(false);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParser.java
new file mode 100644
index 00000000..67ba43b4
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParser.java
@@ -0,0 +1,108 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.Map;
+import tools.vitruv.neojoin.expression_parser.model.MemberFeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.BlockExpressionUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.JvmMemberCallUtils;
+
+import java.util.Optional;
+
+public class MapParser implements ReferenceOperatorParser {
+ private static final String MAP_OPERATION_SIMPLE_NAME = "map";
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ // Check that expression is map and get single argument
+ final Optional mapArgument =
+ CastingUtils.asMemberFeatureCall(expression)
+ .filter(MapParser::isMapOperation)
+ .filter(JvmMemberCallUtils::hasExactlyOneMemberCallArgument)
+ .flatMap(JvmMemberCallUtils::getFirstArgument);
+ if (mapArgument.isEmpty()) {
+ return Optional.empty();
+ }
+
+ // Check that expression can be parsed
+ final Optional mapArgumentExpression =
+ mapArgument
+ .flatMap(CastingUtils::asClosure)
+ .map(XClosure::getExpression)
+ .flatMap(CastingUtils::asBlockExpression)
+ .filter(BlockExpressionUtils::hasExactlyOneExpression)
+ .flatMap(BlockExpressionUtils::getFirstExpression);
+ if (mapArgumentExpression.isEmpty()) {
+ return Optional.empty();
+ }
+
+ final ReferenceOperator mapArgumentOperator =
+ strategy.parseReferenceOperator(mapArgumentExpression.get());
+ if (!(mapArgumentOperator instanceof FeatureCall)) {
+ throw new UnsupportedReferenceExpressionException(
+ "The first element of a map expression must be a feature call",
+ mapArgumentExpression.get());
+ }
+
+ ReferenceOperator currentMapArgumentOperator = mapArgumentOperator.getFollowingOperator();
+ if (currentMapArgumentOperator == null) {
+ throw new UnsupportedReferenceExpressionException(
+ "The map expression must contain more than a feature call",
+ mapArgumentExpression.get());
+ }
+
+ // Loop through each parsed ReferenceOperators inside the map arguments and map/extract them
+ // to the top level ReferenceOperator chain
+ final ReferenceOperator extractedOperatorHead =
+ extractMapArgumentOperator(currentMapArgumentOperator, mapArgumentExpression.get());
+ currentMapArgumentOperator = currentMapArgumentOperator.getFollowingOperator();
+
+ ReferenceOperator currentExtractedOperator = extractedOperatorHead;
+ while (currentMapArgumentOperator != null) {
+ final ReferenceOperator nextOperator =
+ extractMapArgumentOperator(
+ currentMapArgumentOperator, mapArgumentExpression.get());
+
+ currentExtractedOperator.setFollowingOperator(nextOperator);
+ currentExtractedOperator = nextOperator;
+ currentMapArgumentOperator = currentMapArgumentOperator.getFollowingOperator();
+ }
+
+ return parseAndAppendFollowingExpressionOperators(
+ strategy, expression, extractedOperatorHead);
+ }
+
+ private static ReferenceOperator extractMapArgumentOperator(
+ ReferenceOperator mapArgumentOperator, XExpression mapArgumentExpression)
+ throws UnsupportedReferenceExpressionException {
+ // Currently, only one-to-one references are supported inside the map expression, since we
+ // cannot handle the FindAny operator inside nested expression currently. Therefore, all
+ // operators that return a collection are not possible
+ if (mapArgumentOperator instanceof MemberFeatureCall memberFeatureCall
+ && !memberFeatureCall.isCollection()) {
+ return new Map(memberFeatureCall.getFeatureInformation());
+ }
+
+ throw new UnsupportedReferenceExpressionException(
+ "The map expression is not supported", mapArgumentExpression);
+ }
+
+ private static boolean isMapOperation(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmIdentifiableElement::getSimpleName)
+ .map(MAP_OPERATION_SIMPLE_NAME::equals)
+ .orElse(false);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParser.java
new file mode 100644
index 00000000..860fa1f9
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParser.java
@@ -0,0 +1,99 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureInformation;
+import tools.vitruv.neojoin.expression_parser.model.MemberFeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.JvmTypeReferenceUtils;
+
+import java.util.Optional;
+
+public class MemberFeatureCallParser implements ReferenceOperatorParser {
+ private static final String LIST_IDENTIFIER = "java.util.List";
+
+ public Optional parse(
+ PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException {
+ final Optional memberFeatureCall =
+ CastingUtils.asMemberFeatureCall(expression);
+ if (memberFeatureCall.isEmpty()) {
+ return Optional.empty();
+ }
+
+ final Optional jvmField =
+ memberFeatureCall
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmField);
+ if (jvmField.isEmpty()) {
+ return Optional.empty();
+ }
+
+ final Optional foundOperatorOptional;
+ if (MemberFeatureCallParser.isListType(jvmField.get().getType())) {
+ foundOperatorOptional =
+ jvmField.flatMap(MemberFeatureCallParser::getListFeatureInformation)
+ .map(
+ featureInformation ->
+ new MemberFeatureCall(featureInformation, true));
+ } else {
+ foundOperatorOptional =
+ jvmField.flatMap(MemberFeatureCallParser::getFeatureInformation)
+ .map(
+ featureInformation ->
+ new MemberFeatureCall(featureInformation, false));
+ }
+ final ReferenceOperator foundOperator =
+ foundOperatorOptional.orElseThrow(
+ () ->
+ new UnsupportedReferenceExpressionException(
+ "The MemberFeatureCall couldn't be parsed"));
+
+ return parseAndAppendFollowingExpressionOperators(strategy, expression, foundOperator);
+ }
+
+ private static Optional getFeatureInformation(JvmField jvmField) {
+ return Optional.ofNullable(jvmField)
+ .map(JvmField::getType)
+ .flatMap(
+ jvmTypeReference -> {
+ final JvmType jvmType = jvmTypeReference.getType();
+ return Optional.of(
+ new FeatureInformation(
+ jvmField.getSimpleName(),
+ jvmType.getSimpleName(),
+ jvmType.getIdentifier()));
+ });
+ }
+
+ private static boolean isListType(JvmTypeReference typeReference) {
+ return Optional.ofNullable(typeReference)
+ .map(JvmTypeReference::getType)
+ .map(JvmType::getIdentifier)
+ .map(LIST_IDENTIFIER::equals)
+ .orElse(false);
+ }
+
+ private static Optional getListFeatureInformation(JvmField jvmField) {
+ return Optional.ofNullable(jvmField)
+ .map(JvmField::getType)
+ .flatMap(CastingUtils::asParameterizedTypeReference)
+ .filter(JvmTypeReferenceUtils::hasExactlyOneArgument)
+ .flatMap(JvmTypeReferenceUtils::getFirstArgument)
+ .flatMap(CastingUtils::asParameterizedTypeReference)
+ .map(
+ field ->
+ new FeatureInformation(
+ jvmField.getSimpleName(),
+ field.getType().getSimpleName(),
+ field.getType().getIdentifier()));
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ReferenceOperatorParser.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ReferenceOperatorParser.java
new file mode 100644
index 00000000..5ddc386a
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ReferenceOperatorParser.java
@@ -0,0 +1,51 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.jspecify.annotations.Nullable;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.PatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils.CastingUtils;
+
+import java.util.Optional;
+
+public interface ReferenceOperatorParser {
+ Optional parse(PatternMatchingStrategy strategy, XExpression expression)
+ throws UnsupportedReferenceExpressionException;
+
+ /** Returns the next call target if the expression is a {@code XMemberFeatureCall} */
+ default Optional findNextCallTarget(XExpression expression) {
+ return CastingUtils.asMemberFeatureCall(expression)
+ .map(XMemberFeatureCall::getMemberCallTarget)
+ .flatMap(CastingUtils::asAbstractFeatureCall);
+ }
+
+ /**
+ * Runs the Reference operator extraction on the following {@code memberCallTarget} and appends
+ * it to the existing ReferenceOperator chain
+ */
+ default Optional parseAndAppendFollowingExpressionOperators(
+ PatternMatchingStrategy strategy,
+ XExpression currentExpression,
+ @Nullable ReferenceOperator currentOperator)
+ throws UnsupportedReferenceExpressionException {
+ final Optional nextMemberCallTarget =
+ findNextCallTarget(currentExpression);
+ if (nextMemberCallTarget.isPresent()) {
+ // Parse the following expression
+ final ReferenceOperator followingOperator =
+ strategy.parseReferenceOperator(nextMemberCallTarget.get());
+
+ // As the Expression "AST" contains the expressions in reverse order, the current
+ // expression should come after the expression we parsed afterward
+ followingOperator.getLastOperatorInChain().setFollowingOperator(currentOperator);
+ return Optional.of(followingOperator);
+ }
+
+ // If there is no following operator, we are at the end of the expression chain
+ return Optional.ofNullable(currentOperator);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/BlockExpressionUtils.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/BlockExpressionUtils.java
new file mode 100644
index 00000000..4ef2d689
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/BlockExpressionUtils.java
@@ -0,0 +1,35 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.xtext.xbase.XBlockExpression;
+import org.eclipse.xtext.xbase.XExpression;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class BlockExpressionUtils {
+ public static boolean hasNoExpressions(XBlockExpression blockExpression) {
+ return Optional.ofNullable(blockExpression)
+ .map(XBlockExpression::getExpressions)
+ .map(EList::isEmpty)
+ .orElse(false);
+ }
+
+ public static boolean hasExactlyOneExpression(XBlockExpression blockExpression) {
+ return Optional.ofNullable(blockExpression)
+ .map(XBlockExpression::getExpressions)
+ .map(expressions -> expressions.size() == 1)
+ .orElse(false);
+ }
+
+ public static Optional getFirstExpression(XBlockExpression blockExpression) {
+ return Optional.ofNullable(blockExpression)
+ .map(XBlockExpression::getExpressions)
+ .map(EList::stream)
+ .flatMap(Stream::findFirst);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/CastingUtils.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/CastingUtils.java
new file mode 100644
index 00000000..60d504a3
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/CastingUtils.java
@@ -0,0 +1,69 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.JvmFormalParameter;
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XBlockExpression;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import java.util.Optional;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CastingUtils {
+ public static Optional cast(Object object, Class clazz) {
+ return Optional.ofNullable(object).filter(clazz::isInstance).map(clazz::cast);
+ }
+
+ public static Optional asBlockExpression(XExpression expression) {
+ return cast(expression, XBlockExpression.class);
+ }
+
+ public static Optional asClosure(XExpression expression) {
+ return cast(expression, XClosure.class);
+ }
+
+ public static Optional asMemberFeatureCall(XExpression expression) {
+ return cast(expression, XMemberFeatureCall.class);
+ }
+
+ public static Optional asFeatureCall(XExpression expression) {
+ return cast(expression, XFeatureCall.class);
+ }
+
+ public static Optional asAbstractFeatureCall(XExpression expression) {
+ return cast(expression, XAbstractFeatureCall.class);
+ }
+
+ public static Optional asJvmField(JvmIdentifiableElement jvmIdentifiableElement) {
+ return cast(jvmIdentifiableElement, JvmField.class);
+ }
+
+ public static Optional asJvmOperation(
+ JvmIdentifiableElement jvmIdentifiableElement) {
+ return cast(jvmIdentifiableElement, JvmOperation.class);
+ }
+
+ public static Optional asJvmFormalParameter(
+ JvmIdentifiableElement jvmIdentifiableElement) {
+ return cast(jvmIdentifiableElement, JvmFormalParameter.class);
+ }
+
+ public static Optional asParameterizedTypeReference(
+ JvmTypeReference typeReference) {
+ return cast(typeReference, JvmParameterizedTypeReference.class);
+ }
+
+ public static Optional asBinaryOperation(XExpression expression) {
+ return cast(expression, XBinaryOperation.class);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmMemberCallUtils.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmMemberCallUtils.java
new file mode 100644
index 00000000..ccbad70d
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmMemberCallUtils.java
@@ -0,0 +1,28 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JvmMemberCallUtils {
+ public static boolean hasExactlyOneMemberCallArgument(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XMemberFeatureCall::getMemberCallArguments)
+ .map(args -> args.size() == 1)
+ .orElse(false);
+ }
+
+ public static Optional getFirstArgument(XMemberFeatureCall featureCall) {
+ return Optional.ofNullable(featureCall)
+ .map(XMemberFeatureCall::getMemberCallArguments)
+ .map(EList::stream)
+ .flatMap(Stream::findFirst);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmTypeReferenceUtils.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmTypeReferenceUtils.java
new file mode 100644
index 00000000..ef037414
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/JvmTypeReferenceUtils.java
@@ -0,0 +1,29 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JvmTypeReferenceUtils {
+ public static boolean hasExactlyOneArgument(JvmParameterizedTypeReference typeReference) {
+ return Optional.ofNullable(typeReference)
+ .map(JvmParameterizedTypeReference::getArguments)
+ .map(args -> args.size() == 1)
+ .orElse(false);
+ }
+
+ public static Optional getFirstArgument(
+ JvmParameterizedTypeReference typeReference) {
+ return Optional.ofNullable(typeReference)
+ .map(JvmParameterizedTypeReference::getArguments)
+ .map(EList::stream)
+ .flatMap(Stream::findFirst);
+ }
+}
diff --git a/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/PredicateExpressionUtils.java b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/PredicateExpressionUtils.java
new file mode 100644
index 00000000..50e378b3
--- /dev/null
+++ b/lang/expression-parser/parser/src/main/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/utils/PredicateExpressionUtils.java
@@ -0,0 +1,103 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.Value;
+
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XAbstractFeatureCall;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XBooleanLiteral;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XNumberLiteral;
+import org.eclipse.xtext.xbase.XStringLiteral;
+
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ComparisonOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ConstantValue;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+
+import java.util.Map;
+import java.util.Optional;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PredicateExpressionUtils {
+ private static final Map OPERATOR_MAP =
+ Map.ofEntries(
+ Map.entry("operator_equals", ComparisonOperator.Equals),
+ Map.entry("operator_notEquals", ComparisonOperator.NotEquals),
+ Map.entry("operator_lessThan", ComparisonOperator.LessThan),
+ Map.entry("operator_lessEqualsThan", ComparisonOperator.LessEquals),
+ Map.entry("operator_greaterThan", ComparisonOperator.GreaterThan),
+ Map.entry("operator_greaterEqualsThan", ComparisonOperator.GreaterEquals));
+
+ /**
+ * Extracts a ConstantPredicate out of the binary operation. The order of operations is expected
+ * to be FIELD - COMPARISON_OPERATOR - CONSTANT_VALUE
+ *
+ * @throws UnsupportedReferenceExpressionException If the expression doesn't match the expected
+ * format
+ */
+ public static ConstantPredicate extractConstantPredicate(XBinaryOperation operation)
+ throws UnsupportedReferenceExpressionException {
+ // Left side should be a feature/field call
+ final XExpression leftOperand = operation.getLeftOperand();
+ final Optional fieldSimpleName =
+ Optional.ofNullable(leftOperand)
+ .flatMap(CastingUtils::asMemberFeatureCall)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmField)
+ .map(JvmField::getSimpleName);
+ if (fieldSimpleName.isEmpty()) {
+ throw new UnsupportedReferenceExpressionException(
+ String.format(
+ "The left side of a filter expression must be a field, but was: %s",
+ leftOperand));
+ }
+
+ final Optional comparisonOperator = getComparisonOperator(operation);
+ if (comparisonOperator.isEmpty()) {
+ throw new UnsupportedReferenceExpressionException(
+ "The comparison operator is not supported");
+ }
+
+ // Right side should be a constant
+ final XExpression rightOperand = operation.getRightOperand();
+ final Optional constantValue = getConstantExpressionAsString(rightOperand);
+ if (constantValue.isEmpty()) {
+ throw new UnsupportedReferenceExpressionException(
+ String.format(
+ "The right side of a filter expression must be a constant, but was: %s",
+ rightOperand));
+ }
+
+ return new ConstantPredicate(
+ fieldSimpleName.get(), comparisonOperator.get(), constantValue.get());
+ }
+
+ private static Optional getComparisonOperator(XBinaryOperation operation) {
+ return Optional.ofNullable(operation)
+ .map(XAbstractFeatureCall::getFeature)
+ .flatMap(CastingUtils::asJvmOperation)
+ .map(JvmOperation::getSimpleName)
+ .map(OPERATOR_MAP::get);
+ }
+
+ private static Optional getConstantExpressionAsString(XExpression expression) {
+ if (expression instanceof XNumberLiteral numberLiteral) {
+ return Optional.of(ConstantValue.of(numberLiteral.getValue()));
+ } else if (expression instanceof XStringLiteral stringLiteral) {
+ return Optional.of(ConstantValue.fromString(stringLiteral.getValue()));
+ } else if (expression instanceof XBooleanLiteral booleanLiteral) {
+ return Optional.of(ConstantValue.fromBoolean(booleanLiteral.isIsTrue()));
+ }
+ return Optional.empty();
+ }
+
+ @Value
+ public static class ConstantPredicate {
+ String feature;
+ ComparisonOperator operator;
+ ConstantValue constantValue;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFieldFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFieldFixtures.java
new file mode 100644
index 00000000..58d4ecd3
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFieldFixtures.java
@@ -0,0 +1,10 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.TypesFactory;
+
+public class JvmFieldFixtures {
+ public static JvmField createJvmField() {
+ return TypesFactory.eINSTANCE.createJvmField();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFormalParameterFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFormalParameterFixtures.java
new file mode 100644
index 00000000..f3949974
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmFormalParameterFixtures.java
@@ -0,0 +1,17 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmFormalParameter;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.common.types.TypesFactory;
+
+public class JvmFormalParameterFixtures {
+ public static JvmFormalParameter createJvmFormalParameter() {
+ return TypesFactory.eINSTANCE.createJvmFormalParameter();
+ }
+
+ public static JvmFormalParameter createJvmFormalParameter(JvmTypeReference typeReference) {
+ final JvmFormalParameter formalParameter = createJvmFormalParameter();
+ formalParameter.setParameterType(typeReference);
+ return formalParameter;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmOperationFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmOperationFixtures.java
new file mode 100644
index 00000000..1db5fc4a
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmOperationFixtures.java
@@ -0,0 +1,10 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.common.types.TypesFactory;
+
+public class JvmOperationFixtures {
+ public static JvmOperation createJvmOperation() {
+ return TypesFactory.eINSTANCE.createJvmOperation();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeFixtures.java
new file mode 100644
index 00000000..fb1dde1a
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeFixtures.java
@@ -0,0 +1,14 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmGenericType;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.TypesFactory;
+
+public class JvmTypeFixtures {
+ public static JvmType createJvmType(String identifier, String simpleName) {
+ final JvmGenericType jvmType = TypesFactory.eINSTANCE.createJvmGenericType();
+ jvmType.setIdentifier(identifier);
+ jvmType.setSimpleName(simpleName);
+ return jvmType;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeReferenceFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeReferenceFixtures.java
new file mode 100644
index 00000000..01166d80
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/JvmTypeReferenceFixtures.java
@@ -0,0 +1,19 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.common.types.TypesFactory;
+
+public class JvmTypeReferenceFixtures {
+ public static JvmTypeReference createJvmTypeReference(JvmType type) {
+ final JvmParameterizedTypeReference jvmTypeReference =
+ TypesFactory.eINSTANCE.createJvmParameterizedTypeReference();
+ jvmTypeReference.setType(type);
+ return jvmTypeReference;
+ }
+
+ public static JvmParameterizedTypeReference createJvmParameterizedTypeReference() {
+ return TypesFactory.eINSTANCE.createJvmParameterizedTypeReference();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBinaryOperationFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBinaryOperationFixtures.java
new file mode 100644
index 00000000..f31eafb2
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBinaryOperationFixtures.java
@@ -0,0 +1,21 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmIdentifiableElement;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.impl.XBinaryOperationImplCustom;
+
+public class XBinaryOperationFixtures {
+ public static XBinaryOperation createXBinaryOperation() {
+ return new XBinaryOperationImplCustom();
+ }
+
+ public static XBinaryOperation binaryOperation(
+ XExpression leftOperand, JvmIdentifiableElement feature, XExpression rightOperand) {
+ final XBinaryOperation binaryOperation = createXBinaryOperation();
+ binaryOperation.setLeftOperand(leftOperand);
+ binaryOperation.setFeature(feature);
+ binaryOperation.setRightOperand(rightOperand);
+ return binaryOperation;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBlockExpressionFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBlockExpressionFixtures.java
new file mode 100644
index 00000000..af19af48
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XBlockExpressionFixtures.java
@@ -0,0 +1,10 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.xbase.XBlockExpression;
+import org.eclipse.xtext.xbase.impl.XBlockExpressionImplCustom;
+
+public class XBlockExpressionFixtures {
+ public static XBlockExpression createXBlockExpression() {
+ return new XBlockExpressionImplCustom();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XClosureFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XClosureFixtures.java
new file mode 100644
index 00000000..adb2f4ef
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XClosureFixtures.java
@@ -0,0 +1,10 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.impl.XClosureImplCustom;
+
+public class XClosureFixtures {
+ public static XClosure createXClosure() {
+ return new XClosureImplCustom();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XFeatureCallFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XFeatureCallFixtures.java
new file mode 100644
index 00000000..2685c19a
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XFeatureCallFixtures.java
@@ -0,0 +1,32 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmFormalParameter;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.eclipse.xtext.xbase.impl.XFeatureCallImplCustom;
+
+public class XFeatureCallFixtures {
+ public static XFeatureCall createXFeatureCall() {
+ return new XFeatureCallImplCustom();
+ }
+
+ public static XFeatureCall featureCallWithEmptyFormalParameter() {
+ final JvmFormalParameter emptyFormalParameter =
+ JvmFormalParameterFixtures.createJvmFormalParameter();
+ final XFeatureCall featureCall = XFeatureCallFixtures.createXFeatureCall();
+ featureCall.setFeature(emptyFormalParameter);
+ return featureCall;
+ }
+
+ public static XFeatureCall featureCall(String identifier, String simpleName) {
+ final JvmType jvmType = JvmTypeFixtures.createJvmType(identifier, simpleName);
+ final JvmTypeReference jvmTypeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(jvmType);
+ final JvmFormalParameter formalParameter =
+ JvmFormalParameterFixtures.createJvmFormalParameter(jvmTypeReference);
+ final XFeatureCall featureCall = XFeatureCallFixtures.createXFeatureCall();
+ featureCall.setFeature(formalParameter);
+ return featureCall;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XMemberFeatureCallFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XMemberFeatureCallFixtures.java
new file mode 100644
index 00000000..94554328
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XMemberFeatureCallFixtures.java
@@ -0,0 +1,119 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XBlockExpression;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XExpression;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.eclipse.xtext.xbase.impl.XMemberFeatureCallImplCustom;
+
+public class XMemberFeatureCallFixtures {
+ public static XMemberFeatureCall createXMemberFeatureCall() {
+ return new XMemberFeatureCallImplCustom();
+ }
+
+ public static XMemberFeatureCall simpleFieldXMemberFeatureCall(String simpleName) {
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName(simpleName);
+
+ final XMemberFeatureCall memberFeatureCall = createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+
+ return memberFeatureCall;
+ }
+
+ public static XMemberFeatureCall oneToOneFieldXMemberFeatureCall(
+ String identifier, String simpleName, String referenceName) {
+ final JvmType jvmType = JvmTypeFixtures.createJvmType(identifier, simpleName);
+ final JvmTypeReference typeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(jvmType);
+
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName(referenceName);
+ jvmField.setType(typeReference);
+
+ final XMemberFeatureCall memberFeatureCall = createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+
+ return memberFeatureCall;
+ }
+
+ public static XMemberFeatureCall oneToManyFieldXMemberFeatureCall(
+ String identifier, String simpleName, String referenceName) {
+ final JvmType listJvmType = JvmTypeFixtures.createJvmType("java.util.List", null);
+ final JvmParameterizedTypeReference typeReference =
+ JvmTypeReferenceFixtures.createJvmParameterizedTypeReference();
+ typeReference.setType(listJvmType);
+
+ final JvmType innerJvmType = JvmTypeFixtures.createJvmType(identifier, simpleName);
+ final JvmTypeReference innerTypeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(innerJvmType);
+ typeReference.getArguments().add(innerTypeReference);
+
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName(referenceName);
+ jvmField.setType(typeReference);
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+
+ return memberFeatureCall;
+ }
+
+ public static XMemberFeatureCall mapOperationMemberFeatureCall(XExpression innerExpression) {
+ final XClosure closure = XClosureFixtures.createXClosure();
+ final XBlockExpression blockExpression = XBlockExpressionFixtures.createXBlockExpression();
+ blockExpression.getExpressions().add(innerExpression);
+ closure.setExpression(blockExpression);
+
+ final XMemberFeatureCall mapMemberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ mapMemberFeatureCall.getMemberCallArguments().add(closure);
+
+ final JvmOperation mapOperation = JvmOperationFixtures.createJvmOperation();
+ mapOperation.setSimpleName("map");
+ mapMemberFeatureCall.setFeature(mapOperation);
+
+ return mapMemberFeatureCall;
+ }
+
+ public static XMemberFeatureCall flatMapOperationMemberFeatureCall(
+ XExpression innerExpression) {
+ final XClosure closure = XClosureFixtures.createXClosure();
+ final XBlockExpression blockExpression = XBlockExpressionFixtures.createXBlockExpression();
+ blockExpression.getExpressions().add(innerExpression);
+ closure.setExpression(blockExpression);
+
+ final XMemberFeatureCall flatMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ flatMapMemberFeatureCall.getMemberCallArguments().add(closure);
+
+ final JvmOperation flatMapOperation = JvmOperationFixtures.createJvmOperation();
+ flatMapOperation.setSimpleName("flatMap");
+ flatMapMemberFeatureCall.setFeature(flatMapOperation);
+
+ return flatMapMemberFeatureCall;
+ }
+
+ public static XMemberFeatureCall filterOperationMemberFeatureCall(XExpression innerExpression) {
+ final XClosure closure = XClosureFixtures.createXClosure();
+ final XBlockExpression blockExpression = XBlockExpressionFixtures.createXBlockExpression();
+ blockExpression.getExpressions().add(innerExpression);
+ closure.setExpression(blockExpression);
+
+ final XMemberFeatureCall filterMemberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ filterMemberFeatureCall.getMemberCallArguments().add(closure);
+
+ final JvmOperation filterOperation = JvmOperationFixtures.createJvmOperation();
+ filterOperation.setSimpleName("filter");
+ filterMemberFeatureCall.setFeature(filterOperation);
+
+ return filterMemberFeatureCall;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XNumberLiteralFixtures.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XNumberLiteralFixtures.java
new file mode 100644
index 00000000..99fa9727
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/fixtures/XNumberLiteralFixtures.java
@@ -0,0 +1,16 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures;
+
+import org.eclipse.xtext.xbase.XNumberLiteral;
+import org.eclipse.xtext.xbase.XbaseFactory;
+
+public class XNumberLiteralFixtures {
+ public static XNumberLiteral createXNumberLiteral() {
+ return XbaseFactory.eINSTANCE.createXNumberLiteral();
+ }
+
+ public static XNumberLiteral XNumberLiteralWithValue(String value) {
+ final XNumberLiteral numberLiteral = createXNumberLiteral();
+ numberLiteral.setValue(value);
+ return numberLiteral;
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParserTest.java
new file mode 100644
index 00000000..87d3df6b
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/CollectReferencesParserTest.java
@@ -0,0 +1,51 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XBlockExpressionFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XClosureFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+
+import java.util.Optional;
+
+class CollectReferencesParserTest implements ExpressionParserTest {
+ private static final CollectReferencesParser parser = new CollectReferencesParser();
+
+ @Test
+ public void parseToList() throws UnsupportedReferenceExpressionException {
+ // given
+ final JvmOperation toListOperation = JvmOperationFixtures.createJvmOperation();
+ toListOperation.setSimpleName("toList");
+
+ final XClosure closure = XClosureFixtures.createXClosure();
+ closure.setExpression(XBlockExpressionFixtures.createXBlockExpression());
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(toListOperation);
+ memberFeatureCall.getMemberCallArguments().add(closure);
+ memberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, memberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertNull(result);
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ExpressionParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ExpressionParserTest.java
new file mode 100644
index 00000000..a1ab6662
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/ExpressionParserTest.java
@@ -0,0 +1,90 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.FeatureInformation;
+import tools.vitruv.neojoin.expression_parser.model.Map;
+import tools.vitruv.neojoin.expression_parser.model.MemberFeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XFeatureCallFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+
+interface ExpressionParserTest {
+ default XMemberFeatureCall exampleExpressionChain() {
+ final XFeatureCall featureCall =
+ XFeatureCallFixtures.featureCall("some.feature.Call", "SomeCall");
+
+ final XMemberFeatureCall oneToOneMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.oneToOne.member", "MemberFeatureCall", "oneToOneReference");
+ oneToOneMemberFeatureCall.setMemberCallTarget(featureCall);
+
+ final XMemberFeatureCall oneToManyMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToManyFieldXMemberFeatureCall(
+ "some.oneToMany.member", "ManyMembers", "oneToManyRef");
+ oneToManyMemberFeatureCall.setMemberCallTarget(oneToOneMemberFeatureCall);
+
+ final XFeatureCall mapExpressionFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+ final XMemberFeatureCall mapExpressionMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.test.package.SomeMapMemberFeatureCall",
+ "SomeMapMemberSimpleName",
+ "someMapMemberReference");
+ mapExpressionMemberFeatureCall.setMemberCallTarget(mapExpressionFeatureCall);
+ final XMemberFeatureCall mapOperationMemberFeatureCall =
+ XMemberFeatureCallFixtures.mapOperationMemberFeatureCall(
+ mapExpressionMemberFeatureCall);
+ mapOperationMemberFeatureCall.setMemberCallTarget(oneToManyMemberFeatureCall);
+
+ return mapOperationMemberFeatureCall;
+ }
+
+ default ReferenceOperator assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ ReferenceOperator referenceOperator) {
+ assertInstanceOf(FeatureCall.class, referenceOperator);
+ final FeatureCall firstFeatureCall = (FeatureCall) referenceOperator;
+ assertEquals("some.feature.Call", firstFeatureCall.getIdentifier());
+ assertEquals("SomeCall", firstFeatureCall.getSimpleName());
+
+ final ReferenceOperator secondReferenceOperator = firstFeatureCall.getFollowingOperator();
+ assertInstanceOf(MemberFeatureCall.class, secondReferenceOperator);
+ final MemberFeatureCall oneToOneMemberFeatureCall =
+ (MemberFeatureCall) secondReferenceOperator;
+ assertEquals(
+ new FeatureInformation(
+ "oneToOneReference", "MemberFeatureCall", "some.oneToOne.member"),
+ oneToOneMemberFeatureCall.getFeatureInformation());
+ assertFalse(oneToOneMemberFeatureCall.isCollection());
+
+ final ReferenceOperator thirdReferenceOperator =
+ oneToOneMemberFeatureCall.getFollowingOperator();
+ assertInstanceOf(MemberFeatureCall.class, thirdReferenceOperator);
+ final MemberFeatureCall oneToManyMemberFeatureCall =
+ (MemberFeatureCall) thirdReferenceOperator;
+ assertEquals(
+ new FeatureInformation("oneToManyRef", "ManyMembers", "some.oneToMany.member"),
+ oneToManyMemberFeatureCall.getFeatureInformation());
+ assertTrue(oneToManyMemberFeatureCall.isCollection());
+
+ final ReferenceOperator fourthReferenceOperator =
+ oneToManyMemberFeatureCall.getFollowingOperator();
+ assertInstanceOf(Map.class, fourthReferenceOperator);
+ final Map map = (Map) fourthReferenceOperator;
+ assertEquals(
+ new FeatureInformation(
+ "someMapMemberReference",
+ "SomeMapMemberSimpleName",
+ "some.test.package.SomeMapMemberFeatureCall"),
+ map.getFeatureInformation());
+
+ return map.getFollowingOperator();
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParserTest.java
new file mode 100644
index 00000000..7cfbe254
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FeatureCallParserTest.java
@@ -0,0 +1,74 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.eclipse.xtext.common.types.JvmFormalParameter;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmFormalParameterFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmTypeFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmTypeReferenceFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XFeatureCallFixtures;
+
+import java.util.Optional;
+
+class FeatureCallParserTest {
+ private static final FeatureCallParser parser = new FeatureCallParser();
+
+ @Test
+ public void parseEmptyFeatureCall() {
+ // given
+ final JvmFormalParameter emptyFormalParameter =
+ JvmFormalParameterFixtures.createJvmFormalParameter();
+ final XFeatureCall featureCall = XFeatureCallFixtures.createXFeatureCall();
+ featureCall.setFeature(emptyFormalParameter);
+
+ // when
+ final Optional resultOptional = parser.parse(null, featureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result = resultOptional.get();
+ assertInstanceOf(FeatureCall.class, result);
+
+ final FeatureCall resultFeatureCall = (FeatureCall) result;
+ assertNull(resultFeatureCall.getIdentifier());
+ assertNull(resultFeatureCall.getSimpleName());
+ assertNull(resultFeatureCall.getFollowingOperator());
+ }
+
+ @Test
+ public void parseNonEmptyFeatureCall() {
+ // given
+ final JvmType jvmType = JvmTypeFixtures.createJvmType("my.test.package.Car", "Car");
+ final JvmTypeReference jvmTypeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(jvmType);
+ final JvmFormalParameter formalParameter =
+ JvmFormalParameterFixtures.createJvmFormalParameter(jvmTypeReference);
+ final XFeatureCall featureCall = XFeatureCallFixtures.createXFeatureCall();
+ featureCall.setFeature(formalParameter);
+
+ // when
+ final Optional resultOptional = parser.parse(null, featureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result = resultOptional.get();
+ assertInstanceOf(FeatureCall.class, result);
+
+ final FeatureCall resultAsFeatureCall = (FeatureCall) result;
+ assertEquals("my.test.package.Car", resultAsFeatureCall.getIdentifier());
+ assertEquals("Car", resultAsFeatureCall.getSimpleName());
+ assertNull(resultAsFeatureCall.getFollowingOperator());
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParserTest.java
new file mode 100644
index 00000000..2e0716f9
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FilterParserTest.java
@@ -0,0 +1,63 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.eclipse.xtext.xbase.XNumberLiteral;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.ReferenceFilter;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ComparisonOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ConstantValue;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XBinaryOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XNumberLiteralFixtures;
+
+import java.util.Optional;
+
+class FilterParserTest implements ExpressionParserTest {
+ private static final FilterParser parser = new FilterParser();
+
+ @Test
+ public void parse() throws UnsupportedReferenceExpressionException {
+ // given
+ XMemberFeatureCall leftOperand =
+ XMemberFeatureCallFixtures.simpleFieldXMemberFeatureCall("someField");
+ JvmOperation comparisonOperator = JvmOperationFixtures.createJvmOperation();
+ comparisonOperator.setSimpleName("operator_lessThan");
+ XNumberLiteral rightOperand = XNumberLiteralFixtures.XNumberLiteralWithValue("2.1");
+
+ XBinaryOperation binaryOperation =
+ XBinaryOperationFixtures.binaryOperation(
+ leftOperand, comparisonOperator, rightOperand);
+
+ final XMemberFeatureCall filterMemberFeatureCall =
+ XMemberFeatureCallFixtures.filterOperationMemberFeatureCall(binaryOperation);
+ filterMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, filterMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(ReferenceFilter.class, result);
+
+ final ReferenceFilter resultAsFilter = (ReferenceFilter) result;
+ assertEquals("someField", resultAsFilter.getFeature());
+ assertEquals(ComparisonOperator.LessThan, resultAsFilter.getOperator());
+ assertEquals(ConstantValue.of("2.1"), resultAsFilter.getConstantValue());
+ assertNull(resultAsFilter.getFollowingOperator());
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParserTest.java
new file mode 100644
index 00000000..51930c82
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FindAnyParserTest.java
@@ -0,0 +1,87 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XClosure;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.FindAny;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XBlockExpressionFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XClosureFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+
+import java.util.Optional;
+
+class FindAnyParserTest implements ExpressionParserTest {
+ private static final FindAnyParser parser = new FindAnyParser();
+
+ @Test
+ public void parseFindFirst() throws UnsupportedReferenceExpressionException {
+ // given
+ final JvmOperation findFirstOperation = JvmOperationFixtures.createJvmOperation();
+ findFirstOperation.setSimpleName("findFirst");
+
+ final XClosure closure = XClosureFixtures.createXClosure();
+ closure.setExpression(XBlockExpressionFixtures.createXBlockExpression());
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(findFirstOperation);
+ memberFeatureCall.getMemberCallArguments().add(closure);
+ memberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, memberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(FindAny.class, result);
+
+ final FindAny resultAsFindAny = (FindAny) result;
+ assertNull(resultAsFindAny.getFollowingOperator());
+ }
+
+ @Test
+ public void parseFindLast() throws UnsupportedReferenceExpressionException {
+ // given
+ final JvmOperation findLastOperation = JvmOperationFixtures.createJvmOperation();
+ findLastOperation.setSimpleName("findLast");
+
+ final XClosure closure = XClosureFixtures.createXClosure();
+ closure.setExpression(XBlockExpressionFixtures.createXBlockExpression());
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(findLastOperation);
+ memberFeatureCall.getMemberCallArguments().add(closure);
+ memberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, memberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(FindAny.class, result);
+
+ final FindAny resultAsFindAny = (FindAny) result;
+ assertNull(resultAsFindAny.getFollowingOperator());
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParserTest.java
new file mode 100644
index 00000000..c34b1642
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/FlatMapParserTest.java
@@ -0,0 +1,262 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.common.types.JvmOperation;
+import org.eclipse.xtext.xbase.XBinaryOperation;
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.eclipse.xtext.xbase.XNumberLiteral;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureInformation;
+import tools.vitruv.neojoin.expression_parser.model.FlatMap;
+import tools.vitruv.neojoin.expression_parser.model.Map;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceFilter;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ComparisonOperator;
+import tools.vitruv.neojoin.expression_parser.model.predicate_expression.ConstantValue;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XBinaryOperationFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XFeatureCallFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XNumberLiteralFixtures;
+
+import java.util.Optional;
+
+class FlatMapParserTest implements ExpressionParserTest {
+ private static final FlatMapParser parser = new FlatMapParser();
+
+ @Test
+ public void parseWithSimpleLambdaExpression() throws UnsupportedReferenceExpressionException {
+ // given
+ final XFeatureCall flatMapExpressionFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall flatMapExpressionMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToManyFieldXMemberFeatureCall(
+ "some.test.package.SomeMember",
+ "SomeMemberSimpleName",
+ "someMemberReference");
+ flatMapExpressionMemberFeatureCall.setMemberCallTarget(flatMapExpressionFeatureCall);
+
+ final XMemberFeatureCall flatMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.flatMapOperationMemberFeatureCall(
+ flatMapExpressionMemberFeatureCall);
+ flatMapMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, flatMapMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(FlatMap.class, result);
+
+ final FlatMap resultAsFlatMap = (FlatMap) result;
+ assertEquals(
+ new FeatureInformation(
+ "someMemberReference",
+ "SomeMemberSimpleName",
+ "some.test.package.SomeMember"),
+ resultAsFlatMap.getFeatureInformation());
+ assertNull(resultAsFlatMap.getFollowingOperator());
+ }
+
+ @Test
+ public void parseWithOneToOneAndOneToManyMemberFeatureCalls()
+ throws UnsupportedReferenceExpressionException {
+ // given
+ final XFeatureCall flatMapExpressionFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall flatMapExpressionOneToOneMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.oneToOne.package.SomeMember",
+ "SomeMemberSimpleName",
+ "someMemberReference");
+ flatMapExpressionOneToOneMemberFeatureCall.setMemberCallTarget(
+ flatMapExpressionFeatureCall);
+
+ final XMemberFeatureCall flatMapExpressionDeeperMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.oneToOne.package.SomeDeeperMember",
+ "SomeDeeperMemberSimpleName",
+ "someDeeperMemberReference");
+ flatMapExpressionDeeperMemberFeatureCall.setMemberCallTarget(
+ flatMapExpressionOneToOneMemberFeatureCall);
+
+ final XMemberFeatureCall flatMapExpressionDeepestMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToManyFieldXMemberFeatureCall(
+ "some.oneToMany.package.SomeDeepestMember",
+ "SomeDeepestMemberSimpleName",
+ "someDeepestMemberReference");
+ flatMapExpressionDeepestMemberFeatureCall.setMemberCallTarget(
+ flatMapExpressionDeeperMemberFeatureCall);
+
+ final XMemberFeatureCall flatMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.flatMapOperationMemberFeatureCall(
+ flatMapExpressionDeepestMemberFeatureCall);
+ flatMapMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, flatMapMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(Map.class, result);
+
+ final Map firstMapOperator = (Map) result;
+ assertEquals(
+ new FeatureInformation(
+ "someMemberReference",
+ "SomeMemberSimpleName",
+ "some.oneToOne.package.SomeMember"),
+ firstMapOperator.getFeatureInformation());
+
+ final ReferenceOperator secondOperator = firstMapOperator.getFollowingOperator();
+ assertInstanceOf(Map.class, secondOperator);
+
+ final Map secondMapOperator = (Map) secondOperator;
+ assertEquals(
+ new FeatureInformation(
+ "someDeeperMemberReference",
+ "SomeDeeperMemberSimpleName",
+ "some.oneToOne.package.SomeDeeperMember"),
+ secondMapOperator.getFeatureInformation());
+
+ final ReferenceOperator thirdOperator = secondMapOperator.getFollowingOperator();
+ assertInstanceOf(FlatMap.class, thirdOperator);
+
+ final FlatMap thirdFlatMapOperator = (FlatMap) thirdOperator;
+ assertEquals(
+ new FeatureInformation(
+ "someDeepestMemberReference",
+ "SomeDeepestMemberSimpleName",
+ "some.oneToMany.package.SomeDeepestMember"),
+ thirdFlatMapOperator.getFeatureInformation());
+
+ assertNull(thirdFlatMapOperator.getFollowingOperator());
+ }
+
+ @Test
+ public void parseWithNestedFlatMapContainingFilterAndMap()
+ throws UnsupportedReferenceExpressionException {
+ // given: outer flatMap's closure body is itself another flatMap operation whose inner
+ // closure contains a filter and a map, i.e.
+ // `flatMap(x -> x.flatMap(y -> y.someList.filter(someField < 2.1).map(z -> z.nestedRef)))`
+
+ // Innermost closure body for the nested map: `z.nestedRef`
+ final XFeatureCall nestedMapInnerFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall nestedMapInnerMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.nested.package.NestedMember",
+ "NestedMemberSimpleName",
+ "nestedMemberReference");
+ nestedMapInnerMemberFeatureCall.setMemberCallTarget(nestedMapInnerFeatureCall);
+
+ // Filter predicate `someField < 2.1` used inside the inner flatMap's closure
+ final XMemberFeatureCall filterLeftOperand =
+ XMemberFeatureCallFixtures.simpleFieldXMemberFeatureCall("someField");
+ final JvmOperation comparisonOperator = JvmOperationFixtures.createJvmOperation();
+ comparisonOperator.setSimpleName("operator_lessThan");
+ final XNumberLiteral filterRightOperand =
+ XNumberLiteralFixtures.XNumberLiteralWithValue("2.1");
+ final XBinaryOperation binaryOperation =
+ XBinaryOperationFixtures.binaryOperation(
+ filterLeftOperand, comparisonOperator, filterRightOperand);
+
+ // Inner flatMap closure body: `y.someList.filter(...).map(z -> z.nestedRef)`
+ final XFeatureCall innerFlatMapLambdaParameter =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall someListMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToManyFieldXMemberFeatureCall(
+ "some.nested.package.SomeList",
+ "SomeListSimpleName",
+ "someListReference");
+ someListMemberFeatureCall.setMemberCallTarget(innerFlatMapLambdaParameter);
+
+ final XMemberFeatureCall innerFilterMemberFeatureCall =
+ XMemberFeatureCallFixtures.filterOperationMemberFeatureCall(binaryOperation);
+ innerFilterMemberFeatureCall.setMemberCallTarget(someListMemberFeatureCall);
+
+ final XMemberFeatureCall innerMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.mapOperationMemberFeatureCall(
+ nestedMapInnerMemberFeatureCall);
+ innerMapMemberFeatureCall.setMemberCallTarget(innerFilterMemberFeatureCall);
+
+ // Inner flatMap call: `x.flatMap(y -> ...)` — its target is the outer flatMap lambda
+ // parameter `x`, which is itself treated as a collection that flatMap operates on
+ final XFeatureCall outerFlatMapLambdaParameter =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall innerFlatMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.flatMapOperationMemberFeatureCall(
+ innerMapMemberFeatureCall);
+ innerFlatMapMemberFeatureCall.setMemberCallTarget(outerFlatMapLambdaParameter);
+
+ // Outer flatMap call whose closure body is the inner flatMap call above
+ final XMemberFeatureCall flatMapMemberFeatureCall =
+ XMemberFeatureCallFixtures.flatMapOperationMemberFeatureCall(
+ innerFlatMapMemberFeatureCall);
+ flatMapMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, flatMapMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(FlatMap.class, result);
+
+ final FlatMap flatMapOperator = (FlatMap) result;
+ assertEquals(
+ new FeatureInformation(
+ "someListReference",
+ "SomeListSimpleName",
+ "some.nested.package.SomeList"),
+ flatMapOperator.getFeatureInformation());
+
+ final ReferenceOperator secondOperator = flatMapOperator.getFollowingOperator();
+ assertInstanceOf(ReferenceFilter.class, secondOperator);
+
+ final ReferenceFilter filterOperator = (ReferenceFilter) secondOperator;
+ assertEquals("someField", filterOperator.getFeature());
+ assertEquals(ComparisonOperator.LessThan, filterOperator.getOperator());
+ assertEquals(ConstantValue.of("2.1"), filterOperator.getConstantValue());
+
+ final ReferenceOperator thirdOperator = filterOperator.getFollowingOperator();
+ assertInstanceOf(Map.class, thirdOperator);
+
+ final Map mapOperator = (Map) thirdOperator;
+ assertEquals(
+ new FeatureInformation(
+ "nestedMemberReference",
+ "NestedMemberSimpleName",
+ "some.nested.package.NestedMember"),
+ mapOperator.getFeatureInformation());
+
+ assertNull(mapOperator.getFollowingOperator());
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParserTest.java
new file mode 100644
index 00000000..314d1eb3
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MapParserTest.java
@@ -0,0 +1,142 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.xbase.XFeatureCall;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureInformation;
+import tools.vitruv.neojoin.expression_parser.model.Map;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XFeatureCallFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+
+import java.util.Optional;
+
+class MapParserTest implements ExpressionParserTest {
+ private static final MapParser parser = new MapParser();
+
+ @Test
+ public void parseWithSimpleLambdaExpression() throws UnsupportedReferenceExpressionException {
+ // given
+ final XFeatureCall mapExpressionFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall mapExpressionMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.test.package.SomeMember",
+ "SomeMemberSimpleName",
+ "someMemberReference");
+ mapExpressionMemberFeatureCall.setMemberCallTarget(mapExpressionFeatureCall);
+
+ final XMemberFeatureCall mapMemberFeatureCall =
+ XMemberFeatureCallFixtures.mapOperationMemberFeatureCall(
+ mapExpressionMemberFeatureCall);
+ mapMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, mapMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(Map.class, result);
+
+ final Map resultAsMap = (Map) result;
+ assertEquals(
+ new FeatureInformation(
+ "someMemberReference",
+ "SomeMemberSimpleName",
+ "some.test.package.SomeMember"),
+ resultAsMap.getFeatureInformation());
+ assertNull(resultAsMap.getFollowingOperator());
+ }
+
+ @Test
+ public void parseWithMultipleOneToOneMemberFeatureCalls()
+ throws UnsupportedReferenceExpressionException {
+ // given
+ final XFeatureCall mapExpressionFeatureCall =
+ XFeatureCallFixtures.featureCallWithEmptyFormalParameter();
+
+ final XMemberFeatureCall mapExpressionMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.test.package.SomeMember",
+ "SomeMemberSimpleName",
+ "someMemberReference");
+ mapExpressionMemberFeatureCall.setMemberCallTarget(mapExpressionFeatureCall);
+
+ final XMemberFeatureCall mapExpressionDeeperMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.other.package.SomeDeeperMember",
+ "SomeDeeperMemberSimpleName",
+ "someDeeperMemberReference");
+ mapExpressionDeeperMemberFeatureCall.setMemberCallTarget(mapExpressionMemberFeatureCall);
+
+ final XMemberFeatureCall mapExpressionDeepestMemberFeatureCall =
+ XMemberFeatureCallFixtures.oneToOneFieldXMemberFeatureCall(
+ "some.deep.package.SomeDeepestMember",
+ "SomeDeepestMemberSimpleName",
+ "someDeepestMemberReference");
+ mapExpressionDeepestMemberFeatureCall.setMemberCallTarget(
+ mapExpressionDeeperMemberFeatureCall);
+
+ final XMemberFeatureCall mapMemberFeatureCall =
+ XMemberFeatureCallFixtures.mapOperationMemberFeatureCall(
+ mapExpressionDeepestMemberFeatureCall);
+ mapMemberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, mapMemberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(Map.class, result);
+
+ final Map firstMapOperator = (Map) result;
+ assertEquals(
+ new FeatureInformation(
+ "someMemberReference",
+ "SomeMemberSimpleName",
+ "some.test.package.SomeMember"),
+ firstMapOperator.getFeatureInformation());
+
+ final ReferenceOperator secondOperator = firstMapOperator.getFollowingOperator();
+ assertInstanceOf(Map.class, secondOperator);
+
+ final Map secondMapOperator = (Map) secondOperator;
+ assertEquals(
+ new FeatureInformation(
+ "someDeeperMemberReference",
+ "SomeDeeperMemberSimpleName",
+ "some.other.package.SomeDeeperMember"),
+ secondMapOperator.getFeatureInformation());
+
+ final ReferenceOperator thirdOperator = secondMapOperator.getFollowingOperator();
+ assertInstanceOf(Map.class, thirdOperator);
+
+ final Map thirdMapOperator = (Map) thirdOperator;
+ assertEquals(
+ new FeatureInformation(
+ "someDeepestMemberReference",
+ "SomeDeepestMemberSimpleName",
+ "some.deep.package.SomeDeepestMember"),
+ thirdMapOperator.getFeatureInformation());
+
+ assertNull(thirdMapOperator.getFollowingOperator());
+ }
+}
diff --git a/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParserTest.java b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParserTest.java
new file mode 100644
index 00000000..d439b47a
--- /dev/null
+++ b/lang/expression-parser/parser/src/test/java/tools/vitruv/neojoin/expression_parser/parser/strategy/manual_pattern_matching/parsers/MemberFeatureCallParserTest.java
@@ -0,0 +1,136 @@
+package tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.parsers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.xtext.common.types.JvmField;
+import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
+import org.eclipse.xtext.common.types.JvmType;
+import org.eclipse.xtext.common.types.JvmTypeReference;
+import org.eclipse.xtext.xbase.XMemberFeatureCall;
+import org.junit.jupiter.api.Test;
+
+import tools.vitruv.neojoin.expression_parser.model.FeatureInformation;
+import tools.vitruv.neojoin.expression_parser.model.MemberFeatureCall;
+import tools.vitruv.neojoin.expression_parser.model.ReferenceOperator;
+import tools.vitruv.neojoin.expression_parser.parser.exception.UnsupportedReferenceExpressionException;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.ManualPatternMatchingStrategy;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmFieldFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmTypeFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.JvmTypeReferenceFixtures;
+import tools.vitruv.neojoin.expression_parser.parser.strategy.manual_pattern_matching.fixtures.XMemberFeatureCallFixtures;
+
+import java.util.Optional;
+
+class MemberFeatureCallParserTest implements ExpressionParserTest {
+ private static final MemberFeatureCallParser parser = new MemberFeatureCallParser();
+
+ @Test
+ public void parseOneToOneField() throws UnsupportedReferenceExpressionException {
+ // given
+ final JvmType jvmType =
+ JvmTypeFixtures.createJvmType(
+ "my.test.package.SomeChildField", "SomeChildFieldSimpleName");
+ final JvmTypeReference typeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(jvmType);
+
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName("someChildFieldReference");
+ jvmField.setType(typeReference);
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+ memberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, memberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(MemberFeatureCall.class, result);
+
+ final MemberFeatureCall resultAsMemberFeatureCall = (MemberFeatureCall) result;
+ assertEquals(
+ new FeatureInformation(
+ "someChildFieldReference",
+ "SomeChildFieldSimpleName",
+ "my.test.package.SomeChildField"),
+ resultAsMemberFeatureCall.getFeatureInformation());
+ assertFalse(resultAsMemberFeatureCall.isCollection());
+ assertNull(resultAsMemberFeatureCall.getFollowingOperator());
+ }
+
+ @Test
+ public void parseListField() throws UnsupportedReferenceExpressionException {
+ // given
+ final JvmType listJvmType = JvmTypeFixtures.createJvmType("java.util.List", null);
+ final JvmParameterizedTypeReference typeReference =
+ JvmTypeReferenceFixtures.createJvmParameterizedTypeReference();
+ typeReference.setType(listJvmType);
+
+ final JvmType innerJvmType =
+ JvmTypeFixtures.createJvmType(
+ "my.test.package.SomeChildField", "SomeChildFieldSimpleName");
+ final JvmTypeReference innerTypeReference =
+ JvmTypeReferenceFixtures.createJvmTypeReference(innerJvmType);
+ typeReference.getArguments().add(innerTypeReference);
+
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName("someChildFieldReference");
+ jvmField.setType(typeReference);
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+ memberFeatureCall.setMemberCallTarget(exampleExpressionChain());
+
+ // when
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ final Optional resultOptional =
+ parser.parse(strategy, memberFeatureCall);
+
+ // then
+ assertTrue(resultOptional.isPresent());
+
+ final ReferenceOperator result =
+ assertExampleExpressionChainResultAndGetFollowingReferenceOperator(
+ resultOptional.get());
+ assertInstanceOf(MemberFeatureCall.class, result);
+
+ final MemberFeatureCall resultAsMemberFeatureCall = (MemberFeatureCall) result;
+ assertEquals(
+ new FeatureInformation(
+ "someChildFieldReference",
+ "SomeChildFieldSimpleName",
+ "my.test.package.SomeChildField"),
+ resultAsMemberFeatureCall.getFeatureInformation());
+ assertTrue(resultAsMemberFeatureCall.isCollection());
+ assertNull(resultAsMemberFeatureCall.getFollowingOperator());
+ }
+
+ @Test
+ public void shouldThrowWhenTypeIsNull() {
+ // given
+ final JvmField jvmField = JvmFieldFixtures.createJvmField();
+ jvmField.setSimpleName("someChildFieldReference");
+ jvmField.setType(null);
+
+ final XMemberFeatureCall memberFeatureCall =
+ XMemberFeatureCallFixtures.createXMemberFeatureCall();
+ memberFeatureCall.setFeature(jvmField);
+
+ // when & then
+ final ManualPatternMatchingStrategy strategy = new ManualPatternMatchingStrategy();
+ UnsupportedReferenceExpressionException exception =
+ assertThrows(
+ UnsupportedReferenceExpressionException.class,
+ () -> parser.parse(strategy, memberFeatureCall));
+ assertEquals("The MemberFeatureCall couldn't be parsed", exception.getMessage());
+ }
+}
diff --git a/lang/expression-parser/pom.xml b/lang/expression-parser/pom.xml
new file mode 100644
index 00000000..316074bd
--- /dev/null
+++ b/lang/expression-parser/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+ tools.vitruv
+ tools.vitruv.neojoin
+ 0.1.0-SNAPSHOT
+
+
+ tools.vitruv.neojoin.expression_parser
+ pom
+
+ Expression Parser
+
+
+
+ model
+ parser
+
+
diff --git a/lang/pom.xml b/lang/pom.xml
index 3c1956c4..82eb5fc0 100644
--- a/lang/pom.xml
+++ b/lang/pom.xml
@@ -48,6 +48,7 @@
backend-tgg
frontend
model
+ expression-parser
p2wrappers
@@ -99,6 +100,12 @@
assertj-core
3.27.7
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
org.eclipse.emf
org.eclipse.emf.common