This is an automated email from the ASF dual-hosted git repository.

jsorel pushed a commit to branch feat/filtercopy
in repository https://gitbox.apache.org/repos/asf/sis.git

commit bd9026738e42ae325eb9c1acb1ea7396d867668b
Author: jsorel <johann.so...@geomatys.com>
AuthorDate: Mon May 2 14:44:25 2022 +0200

    Filter: add CopyVisitor implementation
---
 .../apache/sis/internal/filter/CopyVisitor.java    | 232 ++++++++++++
 .../sis/internal/filter/CopyVisitorTest.java       | 403 +++++++++++++++++++++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   1 +
 3 files changed, 636 insertions(+)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
new file mode 100644
index 0000000000..a3847de3c6
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
@@ -0,0 +1,232 @@
+/*
+ * 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.sis.internal.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.sis.util.ArgumentChecks;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.DistanceOperator;
+import org.opengis.filter.Expression;
+import org.opengis.filter.Filter;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.LikeOperator;
+import org.opengis.filter.Literal;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.MatchAction;
+import org.opengis.filter.NilOperator;
+import org.opengis.filter.NullOperator;
+import org.opengis.filter.ValueReference;
+import org.opengis.geometry.Envelope;
+import org.opengis.util.CodeList;
+
+/**
+ * Visitor used to copy expressions and filters from one factory to another.
+ * This class purpose is to offer a way to convert filters to different and
+ * often more specialized and efficient implementations such as for Coverages 
or SQL.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class CopyVisitor<R,T> extends Visitor<R,AtomicReference> {
+
+    private final FilterFactory<T,Object,Object> targetFactory;
+
+    /**
+     * Create a new copy visitor with given factory.
+     *
+     * @param targetFactory not null
+     */
+    public CopyVisitor(FilterFactory<T,?,?> targetFactory) {
+        ArgumentChecks.ensureNonNull("factory", targetFactory);
+        this.targetFactory = (FilterFactory<T, Object, Object>) targetFactory;
+        setBinaryComparisonHandlers(this::copyBinaryComparison);
+        setBinaryTemporalHandlers(this::copyBinaryTemporal);
+        setLogicalHandlers(this::copyLogical);
+        setSpatialHandlers(this::copySpatial);
+        setMathHandlers(this::copyMath);
+    }
+
+    /**
+     * Copy given filter using given factory.
+     *
+     * @param filter filter to copy
+     * @return copied filter.
+     */
+    public Filter<T> copy(Filter<R> filter) {
+        final AtomicReference ref = new AtomicReference();
+        visit(filter, ref);
+        return (Filter) ref.get();
+    }
+
+    /**
+     * Copy given expression using given factory.
+     *
+     * @param expression expression to copy
+     * @return copied expression.
+     */
+    public Expression<T,Object> copy(Expression expression) {
+        final AtomicReference<Expression<T, Object>> ref = new 
AtomicReference();
+        visit(expression, ref);
+        return ref.get();
+    }
+
+    private List<Expression<T,Object>> copyExpressions(List<Expression<? super 
R,?>> source) {
+        final List<Expression<T,Object>> results = new 
ArrayList<>(source.size());
+        for (Expression e : source) {
+            results.add(copy(e));
+        }
+        return results;
+    }
+
+    private List<Filter<T>> copyOperands(List<Filter<R>> source) {
+        final List<Filter<T>> results = new ArrayList<>(source.size());
+        for (Filter<R> e : source) {
+            results.add(copy(e));
+        }
+        return results;
+    }
+
+    private void copyBinaryComparison(Filter<R> t, AtomicReference u) {
+        final BinaryComparisonOperator co = (BinaryComparisonOperator) t;
+        final List<Expression<T, Object>> exps = 
copyExpressions(t.getExpressions());
+        final MatchAction ma = co.getMatchAction();
+        final boolean mc = co.isMatchingCase();
+        final Filter<T> result;
+        switch (t.getOperatorType().identifier()) {
+            case "PropertyIsEqualTo" :              result = 
targetFactory.equal(exps.get(0), exps.get(1), mc, ma); break;
+            case "PropertyIsNotEqualTo" :           result = 
targetFactory.notEqual(exps.get(0), exps.get(1), mc, ma); break;
+            case "PropertyIsLessThan" :             result = 
targetFactory.less(exps.get(0), exps.get(1), mc, ma); break;
+            case "PropertyIsGreaterThan" :          result = 
targetFactory.greater(exps.get(0), exps.get(1), mc, ma); break;
+            case "PropertyIsLessThanOrEqualTo" :    result = 
targetFactory.lessOrEqual(exps.get(0), exps.get(1), mc, ma); break;
+            case "PropertyIsGreaterThanOrEqualTo" : result = 
targetFactory.greaterOrEqual(exps.get(0), exps.get(1), mc, ma); break;
+            default : throw new IllegalArgumentException("Unknowned filter 
type " + t.getOperatorType().identifier());
+        }
+        u.set(result);
+    }
+
+    private void copyBinaryTemporal(Filter<R> t, AtomicReference u) {
+        final List<Expression<T, Object>> exps = 
copyExpressions(t.getExpressions());
+        final Filter<T> result;
+        switch (t.getOperatorType().identifier()) {
+            case "After" :          result = targetFactory.after(exps.get(0), 
exps.get(1)); break;
+            case "Before" :         result = targetFactory.before(exps.get(0), 
exps.get(1)); break;
+            case "Begins" :         result = targetFactory.begins(exps.get(0), 
exps.get(1)); break;
+            case "BegunBy" :        result = 
targetFactory.begunBy(exps.get(0), exps.get(1)); break;
+            case "TContains" :      result = 
targetFactory.tcontains(exps.get(0), exps.get(1)); break;
+            case "During" :         result = targetFactory.during(exps.get(0), 
exps.get(1)); break;
+            case "TEquals" :        result = 
targetFactory.tequals(exps.get(0), exps.get(1)); break;
+            case "TOverlaps" :      result = 
targetFactory.toverlaps(exps.get(0), exps.get(1)); break;
+            case "Meets" :          result = targetFactory.meets(exps.get(0), 
exps.get(1)); break;
+            case "Ends" :           result = targetFactory.ends(exps.get(0), 
exps.get(1)); break;
+            case "OverlappedBy" :   result = 
targetFactory.overlappedBy(exps.get(0), exps.get(1)); break;
+            case "MetBy" :          result = targetFactory.metBy(exps.get(0), 
exps.get(1)); break;
+            case "EndedBy" :        result = 
targetFactory.endedBy(exps.get(0), exps.get(1)); break;
+            case "AnyInteracts" :   result = 
targetFactory.anyInteracts(exps.get(0), exps.get(1)); break;
+            default : throw new IllegalArgumentException("Unknowned filter 
type " + t.getOperatorType().identifier());
+        }
+        u.set(result);
+    }
+
+    private void copyLogical(Filter<R> t, AtomicReference u) {
+        final LogicalOperator co = (LogicalOperator) t;
+        final List<Filter<T>> ops = copyOperands(co.getOperands());
+        final Filter<T> result;
+        switch (t.getOperatorType().identifier()) {
+            case "And" : result = targetFactory.and(ops); break;
+            case "Or" :  result = targetFactory.or(ops); break;
+            case "Not" : result = targetFactory.not(ops.get(0)); break;
+            default : throw new IllegalArgumentException("Unknowned filter 
type " + t.getOperatorType().identifier());
+        }
+        u.set(result);
+    }
+
+    private void copySpatial(Filter<R> t, AtomicReference u) {
+        final List<Expression<T, Object>> exps = 
copyExpressions(t.getExpressions());
+        final Filter<T> result;
+        switch (t.getOperatorType().identifier()) {
+            case "BBOX" :       result = targetFactory.bbox(exps.get(0), 
(Envelope) exps.get(1).apply(null)); break;
+            case "Equals" :     result = targetFactory.equals(exps.get(0), 
exps.get(1)); break;
+            case "Disjoint" :   result = targetFactory.disjoint(exps.get(0), 
exps.get(1)); break;
+            case "Intersects" : result = targetFactory.intersects(exps.get(0), 
exps.get(1)); break;
+            case "Touches" :    result = targetFactory.touches(exps.get(0), 
exps.get(1)); break;
+            case "Crosses" :    result = targetFactory.crosses(exps.get(0), 
exps.get(1)); break;
+            case "Within" :     result = targetFactory.within(exps.get(0), 
exps.get(1)); break;
+            case "Contains" :   result = targetFactory.contains(exps.get(0), 
exps.get(1)); break;
+            case "Overlaps" :   result = targetFactory.overlaps(exps.get(0), 
exps.get(1)); break;
+            case "DWithin" :    result = targetFactory.within(exps.get(0), 
exps.get(1), ((DistanceOperator) t).getDistance()); break;
+            case "Beyond" :     result = targetFactory.beyond(exps.get(0), 
exps.get(1), ((DistanceOperator) t).getDistance()); break;
+            default : throw new IllegalArgumentException("Unknowned filter 
type " + t.getOperatorType().identifier());
+        }
+        u.set(result);
+    }
+
+    private void copyMath(Expression<R,?> t, AtomicReference u) {
+        final List<Expression> exps = (List) 
copyExpressions(t.getParameters());
+        final Expression<T,?> result;
+        switch (t.getFunctionName().toString()) {
+            case FunctionNames.Add : result = targetFactory.add(exps.get(0), 
exps.get(1)); break;
+            case FunctionNames.Subtract : result = 
targetFactory.subtract(exps.get(0), exps.get(1)); break;
+            case FunctionNames.Multiply : result = 
targetFactory.multiply(exps.get(0), exps.get(1)); break;
+            case FunctionNames.Divide : result = 
targetFactory.divide(exps.get(0), exps.get(1)); break;
+            default : throw new IllegalArgumentException("Unknowned expression 
type " + t.getFunctionName().toString());
+        }
+        u.set(result);
+    }
+
+    @Override
+    protected void typeNotFound(CodeList<?> type, Filter<R> filter, 
AtomicReference u) {
+        if (filter instanceof BetweenComparisonOperator) {
+            final BetweenComparisonOperator op = (BetweenComparisonOperator) 
filter;
+            u.set(targetFactory.between(copy(op.getExpression()), 
copy(op.getLowerBoundary()), copy(op.getUpperBoundary())));
+        } else if (filter instanceof LikeOperator) {
+            final LikeOperator<R> op = (LikeOperator) filter;
+            u.set(targetFactory.like(
+                    copy(op.getExpressions().get(0)),
+                    (String) copy(op.getExpressions().get(1)).apply(null),
+                    op.getWildCard(),
+                    op.getSingleChar(),
+                    op.getEscapeChar(),
+                    op.isMatchingCase()));
+        } else if (filter instanceof NilOperator) {
+            final NilOperator<R> op = (NilOperator) filter;
+            u.set(targetFactory.isNil(
+                    copy(op.getExpressions().get(0)),
+                    op.getNilReason().orElse(null)));
+        } else if (filter instanceof NullOperator) {
+            final NullOperator<R> op = (NullOperator) filter;
+            u.set(targetFactory.isNull(copy(op.getExpressions().get(0))));
+        } else {
+            super.typeNotFound(type, filter, u);
+        }
+    }
+
+    @Override
+    protected void typeNotFound(String type, Expression<R, ?> expression, 
AtomicReference u) {
+        if (expression instanceof Literal) {
+            final Literal exp = (Literal) expression;
+            u.set(targetFactory.literal(exp.getValue()));
+        } else if (expression instanceof ValueReference) {
+            final ValueReference exp = (ValueReference) expression;
+            u.set(targetFactory.property(exp.getXPath()));
+        } else {
+            u.set(targetFactory.function(type, 
copyExpressions(expression.getParameters()).toArray(new Expression[0])));
+        }
+    }
+
+}
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/CopyVisitorTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/CopyVisitorTest.java
new file mode 100644
index 0000000000..5fc1ee494b
--- /dev/null
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/CopyVisitorTest.java
@@ -0,0 +1,403 @@
+/*
+ * 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.sis.internal.filter;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
+import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.util.iso.Names;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.opengis.feature.Feature;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.BinarySpatialOperator;
+import org.opengis.filter.DistanceOperator;
+import org.opengis.filter.Expression;
+import org.opengis.filter.Filter;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.InvalidFilterValueException;
+import org.opengis.filter.LikeOperator;
+import org.opengis.filter.Literal;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.MatchAction;
+import org.opengis.filter.NilOperator;
+import org.opengis.filter.NullOperator;
+import org.opengis.filter.ResourceId;
+import org.opengis.filter.SortOrder;
+import org.opengis.filter.SortProperty;
+import org.opengis.filter.TemporalOperator;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.Version;
+import org.opengis.filter.capability.FilterCapabilities;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.util.ScopedName;
+
+/**
+ * Verifies copy from {@link CopyVisitor}.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class CopyVisitorTest extends TestCase {
+
+    /**
+     * Test copy a value reference.
+     */
+    @Test
+    public void copyValueReference() {
+        final FilterFactory<Feature,Object,?> source = 
DefaultFilterFactory.forFeatures();
+        final FilterFactory<Map,Object,Object> target = new MockFactory();
+
+        final Expression<Feature,?> exp = source.property("name");
+        final Expression<Map, Object> result = new 
CopyVisitor<>(target).copy(exp);
+
+        assertTrue(result instanceof MockValueReference);
+    }
+
+    /**
+     * Test copy a function.
+     */
+    @Test
+    public void copyFunction() {
+        final FilterFactory<Feature,Object,?> source = 
DefaultFilterFactory.forFeatures();
+        final FilterFactory<Map,Object,Object> target = new MockFactory();
+
+        final Expression<Feature,?> exp1 = source.property("name");
+        final Expression<Feature,?> exp2 = source.property("crs");
+        final Expression<Feature,?> fct = source.function("ST_GeomFromText", 
exp1, exp2);
+        final Expression<Map, Object> result = new 
CopyVisitor<>(target).copy(fct);
+
+        assertTrue(result instanceof MockFunction);
+        final MockFunction resultfct = (MockFunction) result;
+        assertEquals(2, resultfct.getParameters().size());
+        assertTrue(resultfct.getParameters().get(0) instanceof 
MockValueReference);
+        assertTrue(resultfct.getParameters().get(1) instanceof 
MockValueReference);
+    }
+}
+
+final class MockFactory implements FilterFactory<Map, Object, Object>{
+
+    @Override
+    public FilterCapabilities getCapabilities() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public ResourceId<Map> resourceId(String rid, Version version, Instant 
startTime, Instant endTime) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public <V> ValueReference property(String xpath, Class<V> type) {
+        return new MockValueReference(xpath);
+    }
+
+    @Override
+    public <V> Literal<Map, V> literal(V value) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> equal(Expression<? super Map, ?> 
expression1, Expression<? super Map, ?> expression2, boolean isMatchingCase, 
MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> notEqual(Expression<? super Map, ?> 
expression1, Expression<? super Map, ?> expression2, boolean isMatchingCase, 
MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> less(Expression<? super Map, ?> 
expression1, Expression<? super Map, ?> expression2, boolean isMatchingCase, 
MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> greater(Expression<? super Map, ?> 
expression1, Expression<? super Map, ?> expression2, boolean isMatchingCase, 
MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> lessOrEqual(Expression<? super Map, 
?> expression1, Expression<? super Map, ?> expression2, boolean isMatchingCase, 
MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinaryComparisonOperator<Map> greaterOrEqual(Expression<? super 
Map, ?> expression1, Expression<? super Map, ?> expression2, boolean 
isMatchingCase, MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BetweenComparisonOperator<Map> between(Expression<? super Map, ?> 
expression, Expression<? super Map, ?> lowerBoundary, Expression<? super Map, 
?> upperBoundary) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public LikeOperator<Map> like(Expression<? super Map, ?> expression, 
String pattern, char wildcard, char singleChar, char escape, boolean 
isMatchingCase) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public NullOperator<Map> isNull(Expression<? super Map, ?> expression) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public NilOperator<Map> isNil(Expression<? super Map, ?> expression, 
String nilReason) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public LogicalOperator<Map> and(Collection<? extends Filter<? super Map>> 
operands) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public LogicalOperator<Map> or(Collection<? extends Filter<? super Map>> 
operands) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public LogicalOperator<Map> not(Filter<? super Map> operand) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> bbox(Expression<? super Map, ? extends 
Object> geometry, Envelope bounds) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> equals(Expression<? super Map, ? extends 
Object> geometry1, Expression<? super Map, ? extends Object> geometry2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> disjoint(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> intersects(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> touches(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> crosses(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> within(Expression<? super Map, ? extends 
Object> geometry1, Expression<? super Map, ? extends Object> geometry2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> contains(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public BinarySpatialOperator<Map> overlaps(Expression<? super Map, ? 
extends Object> geometry1, Expression<? super Map, ? extends Object> geometry2) 
{
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public DistanceOperator<Map> beyond(Expression<? super Map, ? extends 
Object> geometry1, Expression<? super Map, ? extends Object> geometry2, 
Quantity<Length> distance) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public DistanceOperator<Map> within(Expression<? super Map, ? extends 
Object> geometry1, Expression<? super Map, ? extends Object> geometry2, 
Quantity<Length> distance) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> after(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> before(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> begins(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> begunBy(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> tcontains(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> during(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> tequals(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> toverlaps(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> meets(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> ends(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> overlappedBy(Expression<? super Map, ? 
extends Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> metBy(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> endedBy(Expression<? super Map, ? extends 
Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public TemporalOperator<Map> anyInteracts(Expression<? super Map, ? 
extends Object> time1, Expression<? super Map, ? extends Object> time2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Expression<Map, Number> add(Expression<? super Map, ? extends 
Number> operand1, Expression<? super Map, ? extends Number> operand2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Expression<Map, Number> subtract(Expression<? super Map, ? extends 
Number> operand1, Expression<? super Map, ? extends Number> operand2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Expression<Map, Number> multiply(Expression<? super Map, ? extends 
Number> operand1, Expression<? super Map, ? extends Number> operand2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Expression<Map, Number> divide(Expression<? super Map, ? extends 
Number> operand1, Expression<? super Map, ? extends Number> operand2) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Expression<Map, ?> function(String name, Expression<? super Map, 
?>[] parameters) {
+        return new MockFunction(name, Arrays.asList(parameters));
+    }
+
+    @Override
+    public SortProperty<Map> sort(ValueReference<? super Map, ?> property, 
SortOrder order) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Citation getVendor() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+}
+
+final class MockValueReference implements ValueReference<Map, Object> {
+
+    private final String xpath;
+
+    public MockValueReference(String xpath) {
+        this.xpath = xpath;
+    }
+
+    @Override
+    public String getXPath() {
+        return xpath;
+    }
+
+    @Override
+    public Object apply(Map input) throws InvalidFilterValueException {
+        return input.get(xpath);
+    }
+
+    @Override
+    public <N> Expression<Map, N> toValueType(Class<N> target) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+}
+
+final class MockFunction implements Expression<Map,Object> {
+
+    private final String name;
+    private final List<Expression<? super Map, ?>> parameters;
+
+    public MockFunction(String name, List<Expression<? super Map, ?>> 
parameters) {
+        this.name = name;
+        this.parameters = parameters;
+    }
+
+    @Override
+    public ScopedName getFunctionName() {
+        return Names.createScopedName(null, null, name);
+    }
+
+    @Override
+    public List<Expression<? super Map, ?>> getParameters() {
+        return parameters;
+    }
+
+    @Override
+    public Object apply(Map input) throws InvalidFilterValueException {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public <N> Expression<Map, N> toValueType(Class<N> target) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+}
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
 
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index eaa9d26d14..16a86d5a21 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -63,6 +63,7 @@ import org.junit.runners.Suite;
     org.apache.sis.filter.BinarySpatialFilterUsingJava2D_Test.class,
     org.apache.sis.internal.feature.AttributeConventionTest.class,
     org.apache.sis.internal.feature.GeometryTypeTest.class,
+    org.apache.sis.internal.filter.CopyVisitorTest.class,
     org.apache.sis.internal.filter.FunctionNamesTest.class,
     org.apache.sis.internal.filter.sqlmm.SQLMMTest.class,
     org.apache.sis.internal.filter.sqlmm.RegistryUsingJTS_Test.class,

Reply via email to