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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,21 @@ public sealed interface FilterExpression extends Serializable
default <U> U map(Function<? super FilterExpression, U> mapper) {
return mapper.apply(this);
}

/**
* Dispatches to the appropriate {@link FilterExpressionVisitor} method based on
* this expression's concrete type.
*
* @param visitor the visitor to dispatch to
* @param <R> the result type
* @return the result of visiting this expression
*/
default <R> R accept(FilterExpressionVisitor<R> visitor) {
if (this instanceof AttributeComparisonExpression e) return visitor.visit(e);
if (this instanceof AttributePresentExpression e) return visitor.visit(e);
if (this instanceof LogicalExpression e) return visitor.visit(e);
if (this instanceof GroupExpression e) return visitor.visit(e);
if (this instanceof ValuePathExpression e) return visitor.visit(e);
throw new IllegalStateException("Unknown FilterExpression type: " + getClass().getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.directory.scim.spec.filter;

/**
* Visitor interface for traversing a SCIM {@link FilterExpression} tree.
*
* <p>Since {@link FilterExpression} is a sealed interface with exactly five permitted
* subtypes, implementations of this visitor are guaranteed to handle all possible
* expression types at compile time.</p>
*
* <p>Usage: call {@link FilterExpression#accept(FilterExpressionVisitor)} to dispatch
* to the appropriate {@code visit} method based on the expression's concrete type.</p>
*
* <p>Example — translating SCIM filters to SQL WHERE clauses:</p>
* <pre>
* class SqlFilterVisitor implements FilterExpressionVisitor&lt;String&gt; {
* &#64;Override
* public String visit(AttributeComparisonExpression expr) {
* return expr.getAttributePath().getAttributeName() + " = ?";
* }
* // ... other visit methods
* }
* String sql = filter.getExpression().accept(new SqlFilterVisitor());
* </pre>
*
* @param <R> the result type produced by visiting each expression node
*/
public interface FilterExpressionVisitor<R> {

/**
* Visits an attribute comparison expression (eq, ne, co, sw, ew, gt, ge, lt, le).
*
* @param expr the comparison expression
* @return the visitor result
*/
R visit(AttributeComparisonExpression expr);

/**
* Visits an attribute presence expression (pr).
*
* @param expr the presence expression
* @return the visitor result
*/
R visit(AttributePresentExpression expr);

/**
* Visits a logical expression (and, or).
*
* @param expr the logical expression containing left and right operands
* @return the visitor result
*/
R visit(LogicalExpression expr);

/**
* Visits a group expression (parenthesized sub-expression, optionally negated with not).
*
* @param expr the group expression
* @return the visitor result
*/
R visit(GroupExpression expr);

/**
* Visits a value path expression (e.g., {@code emails[type eq "work"].value}).
*
* @param expr the value path expression
* @return the visitor result
*/
R visit(ValuePathExpression expr);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.directory.scim.spec.filter;

import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class FilterExpressionVisitorTest {

/**
* A tracking visitor that records which visit overload was called
* and returns the simple class name of the expression type.
*/
static class TrackingVisitor implements FilterExpressionVisitor<String> {
String visitedType;

@Override
public String visit(AttributeComparisonExpression expr) {
visitedType = "AttributeComparisonExpression";
return visitedType;
}

@Override
public String visit(AttributePresentExpression expr) {
visitedType = "AttributePresentExpression";
return visitedType;
}

@Override
public String visit(LogicalExpression expr) {
visitedType = "LogicalExpression";
return visitedType;
}

@Override
public String visit(GroupExpression expr) {
visitedType = "GroupExpression";
return visitedType;
}

@Override
public String visit(ValuePathExpression expr) {
visitedType = "ValuePathExpression";
return visitedType;
}
}

@Test
void accept_withAttributeComparisonExpression_callsCorrectVisitOverload() {
TrackingVisitor visitor = new TrackingVisitor();
AttributeReference attrRef = new AttributeReference("userName");
// Declare as FilterExpression to test polymorphic dispatch
FilterExpression expr = new AttributeComparisonExpression(attrRef, CompareOperator.EQ, "john");

String result = expr.accept(visitor);

assertThat(result).isEqualTo("AttributeComparisonExpression");
assertThat(visitor.visitedType).isEqualTo("AttributeComparisonExpression");
}

@Test
void accept_withAttributePresentExpression_callsCorrectVisitOverload() {
TrackingVisitor visitor = new TrackingVisitor();
AttributeReference attrRef = new AttributeReference("emails");
FilterExpression expr = new AttributePresentExpression(attrRef);

String result = expr.accept(visitor);

assertThat(result).isEqualTo("AttributePresentExpression");
assertThat(visitor.visitedType).isEqualTo("AttributePresentExpression");
}

@Test
void accept_withLogicalExpression_callsCorrectVisitOverload() {
TrackingVisitor visitor = new TrackingVisitor();
AttributeReference leftRef = new AttributeReference("userName");
AttributeReference rightRef = new AttributeReference("displayName");
FilterExpression left = new AttributeComparisonExpression(leftRef, CompareOperator.EQ, "john");
FilterExpression right = new AttributeComparisonExpression(rightRef, CompareOperator.EQ, "doe");
FilterExpression expr = new LogicalExpression(left, LogicalOperator.AND, right);

String result = expr.accept(visitor);

assertThat(result).isEqualTo("LogicalExpression");
assertThat(visitor.visitedType).isEqualTo("LogicalExpression");
}

@Test
void accept_withGroupExpression_callsCorrectVisitOverload() {
TrackingVisitor visitor = new TrackingVisitor();
AttributeReference attrRef = new AttributeReference("userName");
FilterExpression inner = new AttributeComparisonExpression(attrRef, CompareOperator.EQ, "john");
FilterExpression expr = new GroupExpression(false, inner);

String result = expr.accept(visitor);

assertThat(result).isEqualTo("GroupExpression");
assertThat(visitor.visitedType).isEqualTo("GroupExpression");
}

@Test
void accept_withValuePathExpression_callsCorrectVisitOverload() {
TrackingVisitor visitor = new TrackingVisitor();
AttributeReference attrRef = new AttributeReference("emails");
AttributeReference filterRef = new AttributeReference("type");
FilterExpression filterExpr = new AttributeComparisonExpression(filterRef, CompareOperator.EQ, "work");
FilterExpression expr = new ValuePathExpression(attrRef, filterExpr);

String result = expr.accept(visitor);

assertThat(result).isEqualTo("ValuePathExpression");
assertThat(visitor.visitedType).isEqualTo("ValuePathExpression");
}
}
Loading