/**
* Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* icense version 2 and the aforementioned licenses.
*
* This program 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 General
* Public License for more details.
*/
/**
* Part of the diploma thesis of Thomas Everding.
* @author Thomas Everding
*/
package org.n52.ses.eml.v001.filter.comparison;
import java.util.HashSet;
import javax.xml.namespace.QName;
import org.apache.xmlbeans.XmlObject;
import org.n52.ses.api.event.DataTypesMap;
import org.n52.ses.api.event.MapEvent;
import org.n52.ses.eml.v001.filter.expression.ABinaryFilterExpression;
import org.n52.ses.eml.v001.filter.expression.AFilterExpression;
import org.n52.ses.eml.v001.filterlogic.esper.customFunctions.MethodNames;
import net.opengis.fes.x20.BinaryComparisonOpType;
/**
* superclass of all binary comparison filters
*
* @author Thomas Everding
*
*/
/**
* @author matthes
*
*/
public abstract class ABinaryComparisonFilter extends AComparisonFilter {
private static final QName PROPERTY_NAME_QNAME = new QName("http://www.opengis.net/fes/2.0", "PropertyName");
/**
* left part of comparison
*/
protected AFilterExpression first;
/**
* right part of comparison
*/
protected AFilterExpression second;
/**
* initializes the filter
* @param binaryOp the type of operator
* @param propertyNames a set of used property names.
*/
protected void initialize(BinaryComparisonOpType binaryOp, HashSet<Object > propertyNames) {
//TODO parse expression
if (binaryOp.getExpressionArray().length == 2) {
this.first = AFilterExpression.FACTORY.buildFilterExpression(binaryOp.getExpressionArray(0), propertyNames, this);
this.second = AFilterExpression.FACTORY.buildFilterExpression(binaryOp.getExpressionArray(1), propertyNames, this);
}
else if (binaryOp.getExpressionArray().length == 1) {
this.first = AFilterExpression.FACTORY.buildFilterExpression(binaryOp.getExpressionArray(0), propertyNames, this);
XmlObject[] props = binaryOp.selectChildren(PROPERTY_NAME_QNAME);
if (props != null && props.length > 0) {
this.second = AFilterExpression.FACTORY.buildFilterExpression(props[0], propertyNames, this);
}
}
}
/**
* Creates the String for the statement using the propertyExists method.
* @return the statement substring.
*/
public String createUsedPropertyString() {
String result = "(";
boolean noPropertyChecked = true;
boolean hasToBeChecked;
String usedProp;
String usedEvent = "";
String usedField = "";
if (this.first.getUsedProperty() != null) {
usedProp = this.first.getUsedProperty().replace("/", ".");
//check if multiple used properties are registered
if (usedProp.equals(ABinaryFilterExpression.MULTIPLE_USED_PROPERTIES_IDENTIFIER)) {
String mups = createMultipleUsedPropertiesString(this.first);
result += mups;
//check if there is a property check
noPropertyChecked = mups.equals("");
}
else {
if (usedProp.contains(".")) {
usedEvent = usedProp.substring(0, usedProp.indexOf(".")+1);
usedField = usedProp.substring(usedProp.indexOf(".")+1, usedProp.length());
}
else {
usedField = usedProp;
}
//check if field has to be checked
hasToBeChecked = this.checkField(usedField);
if (hasToBeChecked) {
result += MethodNames.PROPERTY_EXISTS_NAME + "("+ usedEvent +"this, \"" + usedField + "\") ";
noPropertyChecked = false;
}
}
}
if (this.second.getUsedProperty() != null) {
if (!noPropertyChecked) {
result += "AND ";
}
usedProp = this.second.getUsedProperty().replace("/", ".");
//check if multiple used properties are registered
if (usedProp.equals(ABinaryFilterExpression.MULTIPLE_USED_PROPERTIES_IDENTIFIER)) {
String mups = createMultipleUsedPropertiesString(this.second);
result += mups;
//check if there is a property check
noPropertyChecked = mups.equals("");
}
else {
if (usedProp.contains(".")) {
usedEvent = usedProp.substring(0, usedProp.indexOf(".")+1);
usedField = usedProp.substring(usedProp.indexOf(".")+1, usedProp.length());
}
else {
usedField = usedProp;
}
//check if field has to be checked
hasToBeChecked = this.checkField(usedField);
if (hasToBeChecked) {
result += MethodNames.PROPERTY_EXISTS_NAME + "("+ usedEvent +"this, \"" + usedField + "\") ";
noPropertyChecked = false;
}
}
}
/*
* If no property has to be checked return an empty string.
*
* If a property has been checked the noPropertyChecked flad is false.
*/
if (noPropertyChecked) {
return "";
}
result += ") AND ";
return result;
}
private String createMultipleUsedPropertiesString(AFilterExpression expr) {
String result = "";
if (expr instanceof ABinaryFilterExpression) {
ABinaryFilterExpression bexpr = (ABinaryFilterExpression) expr;
boolean firstProp = true;
for (String usedProp : bexpr.getUsedPropertyArray()) {
String usedEvent = "";
String usedField = "";
if (usedProp.contains(".")) {
usedEvent = usedProp.substring(0, usedProp.indexOf(".")+1);
usedField = usedProp.substring(usedProp.indexOf(".")+1, usedProp.length());
}
else {
usedField = usedProp;
}
//check if field has to be checked
boolean hasToBeChecked = this.checkField(usedField);
if (hasToBeChecked) {
if (firstProp) {
result += MethodNames.PROPERTY_EXISTS_NAME + "("+ usedEvent +"this, \"" + usedField + "\") ";
}
else {
result += "AND " + MethodNames.PROPERTY_EXISTS_NAME + "("+ usedEvent +"this, \"" + usedField + "\") ";
}
firstProp = false;
}
}
}
return result;
}
/**
* Checks if the statement is using correct comparisons.
* String comparisons need quotes around the string.
* @param complex is complex pattern?
*
* @param str1 left part of the comparison
* @param str2 right part of the comparison
* @return str1 and str2 in an array after changing them.
*/
protected String[] checkDataTypes(boolean complex, String str1, String str2) {
return checkDataTypes(str1, str2, false, complex);
}
private String[] checkDataTypes(String str1, String str2, boolean viceversa, boolean complex) {
/*
* TODO workaround for same property of different patterns (why are
* they not found in the datatypesmap?)
*/
String str1Cut = str1;
String str2Cut = str2;
if (complex) {
if (str1.contains(".")) {
str1Cut = str1.substring(str1.indexOf(".")+1);
}
if (str2.contains(".")) {
str2Cut = str1.substring(str1.indexOf(".")+1);
}
}
if (str1.contains(".") && str2.contains(".")) {
str1Cut = str1.substring(str1.indexOf(".")+1);
str2Cut = str2.substring(str2.indexOf(".")+1);
if (str1Cut.equals(str2Cut)) {
return new String[]{str1, str2};
}
}
boolean changed = false;
/*
* check if first is a string -> check second
*/
Object type = DataTypesMap.getInstance().getDataType(str1Cut);
if (type == String.class) {
if (!str2Cut.startsWith("\"")) {
if (!str2Cut.endsWith("\"")) {
str2Cut = "\""+ str2Cut + "\"";
} else {
str2Cut = "\""+ str2Cut;
}
}
changed = true;
} else if (type == Double.class || type == Long.class) {
/*
* try parsing the second as double -> if fails: exception
*/
try {
Double.parseDouble(str2Cut);
str1Cut = str1;
changed = true;
} catch (NumberFormatException e) {
throw new NumberFormatException("Could not parse '"+ str2Cut +"' as a number. Property '" +
str1Cut +"' was registered as a number by default or by a Publisher.");
}
} else {
if (viceversa) {
/*
* everything went wrong.. users fault
*/
throw new IllegalArgumentException("Could not parse the (Not)EqualToFilter element pair '" +
str1Cut +"', '"+ str2Cut +"'. " +
"Please recheck your expression.");
}
}
/*
* return, if changed, in original order.
*/
if (changed && !viceversa) {
return new String[] {str1Cut, str2Cut};
}
if (viceversa) {
/*
* return in switched order
*/
return new String[] {str2Cut, str1Cut};
}
/*
* call self with other direction.
*/
return checkDataTypes(str2Cut, str1Cut, true, complex);
}
/**
* Checks if a event field is mandatory or has to be checked for existance.
*
* @param usedField name of the event field (property)
*
* @return <code>true</code> if the field has to be checked
*/
private boolean checkField(String usedField) {
/*
* mandatory field that do not have to be checked are:
* - this
* - start time
* - end time
* - causality
* - value
*/
if (usedField.equals(MapEvent.THIS_KEY) ||
usedField.equals(MapEvent.START_KEY) ||
usedField.equals(MapEvent.END_KEY) ||
usedField.equals(MapEvent.CAUSALITY_KEY) ||
usedField.equals(MapEvent.VALUE_KEY)) {
//property has not to be checked
return false;
}
return true;
}
}