/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2005-2006, GeoTools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 */
package org.geotools.data.complex.filter;

import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import junit.framework.TestCase;

import org.geotools.data.DataAccessFinder;
import org.geotools.data.complex.AttributeMapping;
import org.geotools.data.complex.ComplexDataStore;
import org.geotools.data.complex.FeatureTypeMapping;
import org.geotools.data.complex.TestData;
import org.geotools.data.complex.filter.XPath.Step;
import org.geotools.data.complex.filter.XPath.StepList;
import org.geotools.data.feature.FeatureAccess;
import org.geotools.data.feature.FeatureSource2;
import org.geotools.data.feature.memory.MemoryDataAccess;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.iso.TypeBuilder;
import org.geotools.feature.iso.Types;
import org.geotools.filter.FilterFactoryImplNamespaceAware;
import org.geotools.gml3.bindings.GML;
import org.geotools.xlink.bindings.XLINK;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureCollection;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.TypeFactory;
import org.opengis.filter.And;
import org.opengis.filter.BinaryLogicOperator;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Id;
import org.opengis.filter.Or;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.PropertyIsGreaterThan;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.Multiply;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.Intersects;
import org.xml.sax.helpers.NamespaceSupport;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;

/**
 * 
 * @author Gabriel Roldan, Axios Engineering
 * @version $Id: UnmappingFilterVisitorTest.java 25884 2007-06-18 15:12:57Z
 *          groldan $
 * @source $URL:
 *         http://svn.geotools.org/geotools/branches/2.4.x/modules/unsupported/community-schemas/community-schema-ds/src/test/java/org/geotools/data/complex/filter/UnmappingFilterVisitorTest.java $
 * @since 2.4
 */
public class UnmappingFilterVisitorTest extends TestCase {

    private static FilterFactory2 ff = (FilterFactory2) CommonFactoryFinder.getFilterFactory(null);

    private UnmappingFilterVisitor visitor;

    MemoryDataAccess dataStore;

    FeatureTypeMapping mapping;

    AttributeDescriptor targetDescriptor;

    FeatureType targetType;

    protected void setUp() throws Exception {
        super.setUp();
        dataStore = TestData.createDenormalizedWaterQualityResults();
        mapping = TestData.createMappingsGroupByStation(dataStore);
        visitor = new UnmappingFilterVisitor(mapping);
        targetDescriptor = mapping.getTargetFeature();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * Creates a mapping from the test case simple source to a complex type with
     * an "areaOfInfluence" attribute, which is a buffer over the simple
     * "location" attribute and another which is the concatenation of the
     * attributes "anzlic_no" and "project_no"
     * 
     * @return
     * @throws Exception
     */
    private FeatureTypeMapping createSampleDerivedAttributeMappings() throws Exception {
        // create the target type
        TypeFactory tf = new org.geotools.feature.iso.type.TypeFactoryImpl();
        TypeBuilder builder = new TypeBuilder(tf);

        AttributeType areaOfInfluence = builder.name("areaOfInfluence").bind(Polygon.class)
                .attribute();
        AttributeType concatType = builder.name("concatenated").bind(String.class).attribute();

        builder.setName("target");
        builder.addAttribute("areaOfInfluence", areaOfInfluence);
        builder.addAttribute("concatenated", concatType);

        FeatureType targetType = builder.feature();
        AttributeDescriptor targetFeature = tf.createAttributeDescriptor(targetType, targetType
                .getName(), 0, Integer.MAX_VALUE, true, null);

        // create the mapping definition
        List attMappings = new LinkedList();

        NamespaceSupport namespaces = new NamespaceSupport();

        Function aoiExpr = ff.function("buffer", ff.property("location"), ff.literal(10));

        attMappings.add(new AttributeMapping(null, aoiExpr, XPath.steps(targetFeature,
                "areaOfInfluence", namespaces)));

        Function strConcat = ff.function("strConcat", ff.property("anzlic_no"), ff
                .property("project_no"));

        attMappings.add(new AttributeMapping(null, strConcat, XPath.steps(targetFeature,
                "concatenated", namespaces)));

        FeatureSource2 simpleSource = mapping.getSource();
        FeatureTypeMapping mapping = new FeatureTypeMapping(simpleSource, targetFeature,
                attMappings, namespaces);
        return mapping;
    }

    /**
     * Mapping specifies station_no --> wq_plus/@id. A FidFilter over wq_plus
     * type should result in a compare equals filter over the station_no
     * attribute of wq_ir_results simple type.
     */
    public void testUnrollFidMappedToAttribute() throws Exception {
        String fid = "station_no.1";
        Id fidFilter = ff.id(Collections.singleton(ff.featureId(fid)));

        Filter unrolled = (Filter) fidFilter.accept(visitor, null);
        assertNotNull(unrolled);

        FeatureCollection results = (FeatureCollection) mapping.getSource().content(unrolled);
        assertEquals(1, getCount(results));

        Iterator features = results.iterator();
        SimpleFeature unmappedFeature = (SimpleFeature) features.next();
        results.close(features);
        assertNotNull(unmappedFeature);
        Object object = unmappedFeature.getValue("station_no");
        assertEquals(fid, object);
    }

    private int getCount(FeatureCollection features) {
        Iterator iterator = features.iterator();
        int count = 0;
        try {
            while (iterator.hasNext()) {
                iterator.next();
                count++;
            }
        } finally {
            features.close(iterator);
        }
        return count;
    }

    /**
     * If no a specific mapping is defined for the feature id, the same feature
     * id from the originating feature source is used. In such a case, an
     * unrolled FidFilter should stay being a FidFilter.
     */
    public void testUnrollFidToFid() throws Exception {

        AttributeMapping featureMapping = null;

        Name featurePath = mapping.getTargetFeature().getName();
        QName featureName = Types.toQName(featurePath);
        for (Iterator it = mapping.getAttributeMappings().iterator(); it.hasNext();) {
            AttributeMapping attMapping = (AttributeMapping) it.next();
            StepList targetXPath = attMapping.getTargetXPath();
            if (targetXPath.size() > 1) {
                continue;
            }
            Step step = (Step) targetXPath.get(0);
            if (featureName.equals(step.getName())) {
                featureMapping = attMapping;
                break;
            }
        }

        featureMapping.setIdentifierExpression(Expression.NIL);

        this.visitor = new UnmappingFilterVisitor(this.mapping);

        FeatureCollection content = (FeatureCollection) mapping.getSource().content();
        Iterator iterator = content.iterator();
        Feature sourceFeature = (Feature) iterator.next();
        content.close(iterator);

        String fid = sourceFeature.getID();

        Id fidFilter = ff.id(Collections.singleton(ff.featureId(fid)));

        Filter unrolled = (Filter) fidFilter.accept(visitor, null);
        assertNotNull(unrolled);
        assertTrue(unrolled instanceof Id);

        FeatureCollection results = (FeatureCollection) mapping.getSource().content(unrolled);
        assertEquals(1, getCount(results));
        iterator = results.iterator();
        SimpleFeature unmappedFeature = (SimpleFeature) iterator.next();
        results.close(iterator);
        assertEquals(fid, unmappedFeature.getID());
    }

    public void testPropertyName() throws Exception {
        PropertyName ae = ff.property("/wq_plus/measurement/result");
        List unrolled = (List) ae.accept(visitor, null);
        assertNotNull(unrolled);
        assertEquals(1, unrolled.size());

        Expression unmappedExpr = (Expression) unrolled.get(0);
        assertTrue(unmappedExpr instanceof PropertyName);
        PropertyName attExp = (PropertyName) unmappedExpr;
        assertEquals("results_value", attExp.getPropertyName());

        // now try with an AttributeExpression that is not directly mapped to an
        // attribute
        // expresion on the surrogate FeatureType, but to a composite one.
        // For example, create a mapping from the test case simple source to
        // a complex type with an "areaOfInfluence" attribute, which is a buffer
        // over the simple "location" attribute
        // and another which is the concatenation of the attributes "anzlic_no"
        // and "project_no"
        FeatureTypeMapping mapping = createSampleDerivedAttributeMappings();
        targetDescriptor = mapping.getTargetFeature();

        visitor = new UnmappingFilterVisitor(mapping);
        attExp = ff.property("areaOfInfluence");
        List unrolledExpressions = (List) attExp.accept(visitor, null);

        assertNotNull(unrolledExpressions);
        assertEquals(1, unrolledExpressions.size());

        unmappedExpr = (Expression) unrolledExpressions.get(0);
        assertTrue(unmappedExpr instanceof Function);
        Function fe = (Function) unmappedExpr;
        assertEquals("buffer", fe.getName());

        Expression arg0 = (Expression) fe.getParameters().get(0);
        assertTrue(arg0 instanceof PropertyName);
        assertEquals("location", ((PropertyName) arg0).getPropertyName());
    }
    
    /**
     * An xpath expression may target a "client property" mapping (in xml land, an xml attribute
     * rather than a xml element).
     * @throws Exception
     */
    public void testPropertyNameWithXlinkAttribute() throws Exception {
        final String schemaBase = "/test-data/";
        final Map dsParams = new HashMap();
        final URL url = getClass().getResource(schemaBase + "BoreholeTest_properties.xml");
        dsParams.put("dbtype", "complex");
        dsParams.put("url", url.toExternalForm());

        final String XMMLNS = "http://www.opengis.net/xmml";
        final Name typeName = new org.geotools.feature.Name(XMMLNS, "Borehole");

        FeatureAccess mappingDataStore = (FeatureAccess) DataAccessFinder.createAccess(dsParams);
        ComplexDataStore complexDs = (ComplexDataStore) mappingDataStore;
        mapping = complexDs.getMapping(typeName.getLocalPart());

        NamespaceSupport namespaces = new NamespaceSupport();
        namespaces.declarePrefix("gml", GML.NAMESPACE);
        namespaces.declarePrefix("xmml", XMMLNS);
        namespaces.declarePrefix("xlink", XLINK.NAMESPACE);

        FilterFactory2 ff = new FilterFactoryImplNamespaceAware(namespaces);
        String xpathExpression = "sa:shape/geo:LineByVector/geo:origin/@xlink:href";
        PropertyName propNameExpression = ff.property(xpathExpression);
        
        visitor = new UnmappingFilterVisitor(mapping);

        List /*<Expression>*/ unrolled = (List) propNameExpression.accept(visitor, null);
        assertNotNull(unrolled);
        assertEquals(1, unrolled.size());
        assertTrue(unrolled.get(0) instanceof Expression);
    }


    public void testBetweenFilter() throws Exception {
        PropertyIsBetween bf = ff.between(ff.property("measurement/result"), ff.literal(1), ff
                .literal(2));

        PropertyIsBetween unrolled = (PropertyIsBetween) bf.accept(visitor, null);
        Expression att = unrolled.getExpression();
        assertTrue(att instanceof PropertyName);
        String propertyName = ((PropertyName) att).getPropertyName();
        assertEquals("results_value", propertyName);
    }

    /**
     * 
     */
    public void testCompareFilter() throws Exception {
        PropertyIsEqualTo complexFilter = ff.equals(ff.property("measurement/result"), ff
                .literal(1.1));

        Filter unrolled = (Filter) complexFilter.accept(visitor, null);
        assertNotNull(unrolled);
        assertTrue(unrolled instanceof PropertyIsEqualTo);
        assertNotSame(complexFilter, unrolled);

        Expression left = ((PropertyIsEqualTo) unrolled).getExpression1();
        Expression right = ((PropertyIsEqualTo) unrolled).getExpression2();

        assertTrue(left instanceof PropertyName);
        assertTrue(right instanceof Literal);
        PropertyName attExp = (PropertyName) left;
        String expectedAtt = "results_value";
        assertEquals(expectedAtt, attExp.getPropertyName());
        assertEquals(new Double(1.1), ((Literal) right).getValue());
    }

    /**
     * There might be multiple mappings per propery name, like
     * <code>gml:name[1] = att1</code>,
     * <code>gml:name2 = strConcat(att2, att3)</code>,
     * <code>gml:name[3] = "sampleValue</code>.
     * <p>
     * In the BoreHole test mapping used here, the following mappings exist for
     * gml:name:
     * <ul>
     * <li>gml:name[1] = strConcat( strConcat(QS, strConcat("/", RT)),
     * strConcat(strConcat("/", NUMB), strConcat("/", BSUFF)) )</li>
     * <li>gml:name[2] = BGS_ID</li>
     * <li>gml:name[3] = NAME</li>
     * <li>gml:name[4] = ORIGINAL_N</li>
     * </ul>
     * 
     * This means the "unrolled" filter for
     * <code>gml:name = "SWADLINCOTE"</code> should be
     * <code>strConcat( strConcat(QS, ...) = "SWADLINCOTE" 
     *          OR BGS_ID = "SWADLINCOTE" 
     *          OR NAME = "SWADLINCOTE" 
     *          OR ORIGINAL_N = "SWADLINCOTE"</code>
     * <p>
     * 
     * @throws Exception
     */
    public void testCompareFilterMultipleMappingsPerPropertyName() throws Exception {
        final String schemaBase = "/test-data/";
        final Map dsParams = new HashMap();
        final URL url = getClass().getResource(schemaBase + "BoreholeTest_properties.xml");
        dsParams.put("dbtype", "complex");
        dsParams.put("url", url.toExternalForm());

        final String XMMLNS = "http://www.opengis.net/xmml";
        final Name typeName = new org.geotools.feature.Name(XMMLNS, "Borehole");

        FeatureAccess mappingDataStore = (FeatureAccess) DataAccessFinder.createAccess(dsParams);
        ComplexDataStore complexDs = (ComplexDataStore) mappingDataStore;
        mapping = complexDs.getMapping(typeName.getLocalPart());

        NamespaceSupport namespaces = new NamespaceSupport();
        namespaces.declarePrefix("gml", GML.NAMESPACE);
        namespaces.declarePrefix("xmml", XMMLNS);

        FilterFactory2 ff = new FilterFactoryImplNamespaceAware(namespaces);
        PropertyIsEqualTo complexFilter = ff.equals(ff.property("gml:name"), ff
                .literal("SWADLINCOTE"));

        visitor = new UnmappingFilterVisitor(mapping);

        Filter unrolled = (Filter) complexFilter.accept(visitor, null);
        assertNotNull(unrolled);
        assertNotSame(complexFilter, unrolled);

        assertTrue(unrolled.getClass().getName(), unrolled instanceof org.opengis.filter.Or);

        Or oredFilter = (Or) unrolled;
        List children = oredFilter.getChildren();
        assertEquals(4, children.size());

        assertTrue(children.get(0) instanceof PropertyIsEqualTo);
        assertTrue(children.get(1) instanceof PropertyIsEqualTo);
        assertTrue(children.get(2) instanceof PropertyIsEqualTo);
        assertTrue(children.get(3) instanceof PropertyIsEqualTo);

        PropertyIsEqualTo filter1 = (PropertyIsEqualTo) children.get(0);
        PropertyIsEqualTo filter2 = (PropertyIsEqualTo) children.get(1);
        PropertyIsEqualTo filter3 = (PropertyIsEqualTo) children.get(2);
        PropertyIsEqualTo filter4 = (PropertyIsEqualTo) children.get(3);

        assertTrue(filter1.getExpression1() instanceof Function);
        assertTrue(filter2.getExpression1() instanceof PropertyName);
        assertTrue(filter3.getExpression1() instanceof PropertyName);
        assertTrue(filter4.getExpression1() instanceof PropertyName);

        assertTrue(filter1.getExpression2() instanceof Literal);
        assertTrue(filter2.getExpression2() instanceof Literal);
        assertTrue(filter3.getExpression2() instanceof Literal);
        assertTrue(filter4.getExpression2() instanceof Literal);

        assertEquals("BGS_ID", ((PropertyName) filter2.getExpression1()).getPropertyName());
        assertEquals("NAME", ((PropertyName) filter3.getExpression1()).getPropertyName());
        assertEquals("ORIGINAL_N", ((PropertyName) filter4.getExpression1()).getPropertyName());
    }

    /**
     * 
     */
    public void testLogicFilterAnd() throws Exception {
        PropertyIsEqualTo equals = ff.equals(ff.property("measurement/result"), ff.literal(1.1));
        PropertyIsGreaterThan greater = ff.greater(ff
                .property("measurement/determinand_description"), ff.literal("desc1"));

        And logicFilter = ff.and(equals, greater);

        Filter unrolled = (Filter) logicFilter.accept(visitor, null);
        assertNotNull(unrolled);
        assertTrue(unrolled instanceof And);
        assertNotSame(equals, unrolled);

        And sourceAnd = (And) unrolled;
        assertEquals(2, sourceAnd.getChildren().size());

        Filter sourceEquals = (Filter) sourceAnd.getChildren().get(0);
        assertTrue(sourceEquals instanceof PropertyIsEqualTo);

        Expression left = ((PropertyIsEqualTo) sourceEquals).getExpression1();
        Expression right = ((PropertyIsEqualTo) sourceEquals).getExpression2();
        assertTrue(left instanceof PropertyName);
        assertTrue(right instanceof Literal);

        assertEquals("results_value", ((PropertyName) left).getPropertyName());
        assertEquals(new Double(1.1), ((Literal) right).getValue());

        Filter sourceGreater = (Filter) sourceAnd.getChildren().get(1);
        assertTrue(sourceGreater instanceof PropertyIsGreaterThan);

        left = ((PropertyIsGreaterThan) sourceGreater).getExpression1();
        right = ((PropertyIsGreaterThan) sourceGreater).getExpression2();
        assertTrue(left instanceof PropertyName);
        assertTrue(right instanceof Literal);

        assertEquals("determinand_description", ((PropertyName) left).getPropertyName());
        assertEquals("desc1", ((Literal) right).getValue());
    }

    public void testFunction() throws Exception {
        Function fe = ff.function("strIndexOf",
                ff.property("/measurement/determinand_description"), ff
                        .literal("determinand_description_1"));

        List unrolledExpressions = (List) fe.accept(visitor, null);

        Expression unmapped = (Expression) unrolledExpressions.get(0);
        assertTrue(unmapped instanceof Function);
        List params = ((Function) unmapped).getParameters();
        assertEquals(2, params.size());
        assertTrue(params.get(0) instanceof PropertyName);
        assertEquals("determinand_description", ((PropertyName) params.get(0)).getPropertyName());
    }

    public void testGeometryFilter() throws Exception {
        mapping = createSampleDerivedAttributeMappings();
        visitor = new UnmappingFilterVisitor(mapping);
        targetDescriptor = mapping.getTargetFeature();
        targetType = (FeatureType) targetDescriptor.type();

        Expression literalGeom = ff
                .literal(new GeometryFactory().createPoint(new Coordinate(1, 1)));

        Intersects gf = ff.intersects(ff.property("areaOfInfluence"), literalGeom);

        Filter unrolled = (Filter) gf.accept(visitor, null);
        assertTrue(unrolled instanceof Intersects);
        assertNotSame(gf, unrolled);

        Intersects newFilter = (Intersects) unrolled;
        Expression left = newFilter.getExpression1();
        Expression right = newFilter.getExpression2();

        assertSame(right, literalGeom);
        assertTrue(left instanceof Function);
        Function fe = (Function) left;
        assertEquals("buffer", fe.getName());

        Expression arg0 = (Expression) fe.getParameters().get(0);
        assertTrue(arg0 instanceof PropertyName);
        assertEquals("location", ((PropertyName) arg0).getPropertyName());
    }

    public void testLikeFilter() throws Exception {
        final String wildcard = "%";
        final String single = "?";
        final String escape = "\\";
        PropertyIsLike like = ff.like(ff.property("/measurement/determinand_description"),
                "%n_1_1", wildcard, single, escape);

        PropertyIsLike unmapped = (PropertyIsLike) like.accept(visitor, null);
        assertEquals(like.getLiteral(), unmapped.getLiteral());
        assertEquals(like.getWildCard(), unmapped.getWildCard());
        assertEquals(like.getSingleChar(), unmapped.getSingleChar());
        assertEquals(like.getEscape(), unmapped.getEscape());

        Expression unmappedExpr = unmapped.getExpression();
        assertTrue(unmappedExpr instanceof PropertyName);
        assertEquals("determinand_description", ((PropertyName) unmappedExpr).getPropertyName());
    }

    public void testLiteralExpression() throws Exception {
        Expression literal = ff.literal(new Integer(0));
        List unrolledExpressions = (List) literal.accept(visitor, null);
        assertEquals(1, unrolledExpressions.size());
        assertSame(literal, unrolledExpressions.get(0));
    }

    public void testLogicFilter() throws Exception {
        testLogicFilter(And.class);
        testLogicFilter(Or.class);
    }

    private void testLogicFilter(Class filterType) throws Exception {
        BinaryLogicOperator complexLogicFilter;
        PropertyIsGreaterThan resultFilter = ff.greater(ff.property("measurement/result"), ff
                .literal(new Integer(5)));

        PropertyIsBetween determFilter = ff.between(ff
                .property("measurement/determinand_description"), ff
                .literal("determinand_description_1_1"), ff.literal("determinand_description_3_3"));

        if (And.class.equals(filterType)) {
            complexLogicFilter = ff.and(resultFilter, determFilter);
        } else if (Or.class.equals(filterType)) {
            complexLogicFilter = ff.or(resultFilter, determFilter);
        } else {
            throw new IllegalArgumentException();
        }

        Filter unmapped = (Filter) complexLogicFilter.accept(visitor, null);
        assertNotNull(unmapped);
        assertTrue(unmapped instanceof BinaryLogicOperator);
        assertNotSame(complexLogicFilter, unmapped);

        BinaryLogicOperator logicUnmapped = (BinaryLogicOperator) unmapped;

        List children = logicUnmapped.getChildren();
        assertEquals(2, children.size());

        PropertyIsGreaterThan unmappedResult = (PropertyIsGreaterThan) children.get(0);
        PropertyIsBetween unmappedDeterm = (PropertyIsBetween) children.get(1);

        assertEquals("results_value", ((PropertyName) unmappedResult.getExpression1())
                .getPropertyName());

        assertEquals(new Integer(5), ((Literal) unmappedResult.getExpression2()).getValue());

        assertEquals("determinand_description", ((PropertyName) unmappedDeterm.getExpression())
                .getPropertyName());
        assertEquals("determinand_description_1_1", ((Literal) unmappedDeterm.getLowerBoundary())
                .getValue());
        assertEquals("determinand_description_3_3", ((Literal) unmappedDeterm.getUpperBoundary())
                .getValue());
    }

    public void testMathExpression() throws Exception {
        Literal literal = ff.literal(new Integer(2));
        Multiply mathExp = ff.multiply(ff.property("measurement/result"), literal);

        List unrolledExpressions = (List) mathExp.accept(visitor, null);

        assertEquals(1, unrolledExpressions.size());
        Expression unmapped = (Expression) unrolledExpressions.get(0);
        assertTrue(unmapped instanceof Multiply);
        Multiply mathUnmapped = (Multiply) unmapped;

        PropertyName unmappedAttt = (PropertyName) mathUnmapped.getExpression1();
        assertEquals("results_value", unmappedAttt.getPropertyName());
        assertSame(literal, mathUnmapped.getExpression2());
    }

    public void testNullFilter() throws Exception {
        PropertyIsNull nullFilter = ff.isNull(ff.property("measurement/result"));

        Filter unrolled = (Filter) nullFilter.accept(visitor, null);
        assertTrue(unrolled instanceof PropertyIsNull);
        assertNotSame(nullFilter, unrolled);

        PropertyIsNull unmapped = (PropertyIsNull) unrolled;
        Expression unmappedAtt = unmapped.getExpression();
        assertTrue(unmappedAtt instanceof PropertyName);
        assertEquals("results_value", ((PropertyName) unmappedAtt).getPropertyName());
    }

}
