/** * Copyright (c) 2011-2014, OpenIoT * * This file is part of OpenIoT. * * OpenIoT 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 3 of the License. * * OpenIoT 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. * * You should have received a copy of the GNU Lesser General Public License * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>. * * Contact: OpenIoT mailto: info@openiot.eu */ package org.openiot.cupus.common; import java.io.Serializable; import java.util.Locale; import org.openiot.cupus.common.enums.Operator; /** * This class represents a single piece of information that publications and * subscriptions are made of. Publications will always have the operator "equal" * because they represent a point in a space, for example a=5 or s="lol", while * subscriptions can have any operator because they want to match any * publication that satisfies their condition, for example a>=4 or * s.contains("lol"). "a" or "s" is the key, "5" or "lol" is the value and "=", * ">=" or "contains" is the operator. All possible operators are enumerated in * the Operator enumeration. * * @author Eugen Rožić */ public class Triplet implements Serializable { private static final long serialVersionUID = 1L; private String key; /** * Can only possible be String, Double or Double[] */ private Object value; private Operator operator; /** * Should not be used, created only for WS methods */ public Triplet() { } public Triplet(String key, Object value, Operator operator) { this.key = key; this.operator = operator; // check if value matches operator! Double numValue = null; switch (operator) { // string operators... case CONTAINS_STRING: case STARTS_WITH_STRING: case ENDS_WITH_STRING: if (!(value instanceof String)) { throw new RuntimeException("Non string with string operator!"); } this.value = value; break; // numeric operators (one value) case GREATER_THAN: case GREATER_OR_EQUAL: case LESS_THAN: case LESS_OR_EQUAL: numValue = Double.parseDouble(value.toString()); // will throw // exception if // not // numeric... this.value = numValue; break; // numeric (two values) case BETWEEN: if (!(value instanceof Object[])) { if (value.getClass().isArray()) { throw new RuntimeException( "Only object arrays can be used (Double[] for example), not primitive arrays (like double[])!"); } else { throw new RuntimeException( "Non-array with BETWEEN operator!"); } } Object[] arrayValue = (Object[]) value; if (arrayValue.length != 2) { throw new RuntimeException( "Array of size!=2 with BETWEEN operator!"); } Double[] numArray = new Double[2]; numArray[0] = Double.parseDouble(arrayValue[0].toString()); // will // throw // exception // if // not // numeric... numArray[1] = Double.parseDouble(arrayValue[1].toString()); // will // throw // exception // if // not // numeric... if (numArray[0] >= numArray[1]) { throw new RuntimeException( "First number of between has to be LESS THAN (<) the second number!"); } this.value = numArray; break; // only one left... case EQUAL: if (value instanceof String) { this.value = value; } else { numValue = Double.parseDouble(value.toString()); // will throw // exception // if not // numeric... this.value = numValue; } break; default: throw new RuntimeException( "Operator has to be one of the legal enum constants!"); } } public String getKey() { return this.key; } public Operator getOperator() { return this.operator; } public Object getValue() { return this.value; } public void setValue(Object newValue) { this.value = newValue; } /** * Checks if this Triplet covers "that" Triplet. */ public boolean covers(Triplet that) { // checking for extremes... if (!this.key.equals(that.key)) { return false; } else if (this.equals(that)) { return true; } // if this is string both have to be strings... if (this.value instanceof String) { if (!(that.value instanceof String)) { return false; } String thisVal = ((String) this.value).toLowerCase(Locale.ENGLISH); String thatVal = ((String) that.value).toLowerCase(Locale.ENGLISH); // for every possible this string operator check that.operator switch (this.operator) { case EQUAL: switch (that.operator) { case EQUAL: return thisVal.equals(thatVal); case CONTAINS_STRING: case STARTS_WITH_STRING: case ENDS_WITH_STRING: return false; } case CONTAINS_STRING: switch (that.operator) { case EQUAL: case CONTAINS_STRING: case STARTS_WITH_STRING: case ENDS_WITH_STRING: return thatVal.contains(thisVal); } case STARTS_WITH_STRING: switch (that.operator) { case EQUAL: case STARTS_WITH_STRING: return thatVal.startsWith(thisVal); case CONTAINS_STRING: case ENDS_WITH_STRING: return false; } case ENDS_WITH_STRING: switch (that.operator) { case EQUAL: case ENDS_WITH_STRING: return thatVal.endsWith(thisVal); case CONTAINS_STRING: case STARTS_WITH_STRING: return false; } default: System.err .println("Triplet.class: this shouldn't happen! (string)"); return false; } } else { // if not strings then they have to be numbers of some kind if (!(that.value instanceof Number)) { return false; } double thisVal, thatVal; Double[] thisArrayVal, thatArrayVal; // for every possible this numerical operator check that.operator switch (this.operator) { case EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return thisVal == thatVal; case GREATER_OR_EQUAL: case GREATER_THAN: case LESS_OR_EQUAL: case LESS_THAN: case BETWEEN: return false; } case GREATER_THAN: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: case GREATER_OR_EQUAL: thatVal = (Double) that.value; return thisVal < thatVal; case GREATER_THAN: thatVal = (Double) that.value; return thisVal <= thatVal; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal < thatArrayVal[0]; case LESS_OR_EQUAL: case LESS_THAN: return false; } case GREATER_OR_EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: case GREATER_OR_EQUAL: case GREATER_THAN: thatVal = (Double) that.value; return thisVal <= thatVal; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal <= thatArrayVal[0]; case LESS_OR_EQUAL: thatVal = (Double) that.value; return (thisVal == Double.NEGATIVE_INFINITY) && (thatVal == Double.POSITIVE_INFINITY); case LESS_THAN: return false; } case LESS_THAN: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: case LESS_OR_EQUAL: thatVal = (Double) that.value; return thisVal > thatVal; case LESS_THAN: thatVal = (Double) that.value; return thisVal >= thatVal; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal > thatArrayVal[1]; case GREATER_OR_EQUAL: case GREATER_THAN: return false; } case LESS_OR_EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: case LESS_OR_EQUAL: case LESS_THAN: thatVal = (Double) that.value; return thisVal >= thatVal; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal >= thatArrayVal[1]; case GREATER_OR_EQUAL: thatVal = (Double) that.value; return (thatVal == Double.NEGATIVE_INFINITY) && (thisVal == Double.POSITIVE_INFINITY); case GREATER_THAN: return false; } case BETWEEN: thisArrayVal = (Double[]) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return (thisArrayVal[0] <= thatVal) && (thisArrayVal[1] >= thatVal); case BETWEEN: thatArrayVal = (Double[]) that.value; return (thisArrayVal[0] <= thatArrayVal[0]) && (thisArrayVal[1] >= thatArrayVal[1]); case LESS_OR_EQUAL: case LESS_THAN: thatVal = (Double) that.value; return (thisArrayVal[1] >= thatVal) && (thisArrayVal[0] == Double.NEGATIVE_INFINITY); case GREATER_OR_EQUAL: case GREATER_THAN: thatVal = (Double) that.value; return (thisArrayVal[0] <= thatVal) && (thisArrayVal[1] == Double.POSITIVE_INFINITY); } default: System.err .println("Triplet.class: this shouldn't happen! (numeric)"); return false; } } } /** * Checks if this Triplet partially covers "that" Triplet, i.e. an * intersection of this triplet and that triplet is not an empty set. */ public boolean partiallyCovers(Triplet that) { // checking for extremes... if (!this.key.equals(that.key)) { return false; } else if (this.equals(that)) { return true; } // if this is string both have to be strings... if (this.value instanceof String) { if (!(that.value instanceof String)) { return false; } String thisVal = ((String) this.value).toLowerCase(Locale.ENGLISH); String thatVal = ((String) that.value).toLowerCase(Locale.ENGLISH); // for every possible this string operator check that.operator switch (this.operator) { case EQUAL: switch (that.operator) { case EQUAL: return thisVal.equals(thatVal); case CONTAINS_STRING: case STARTS_WITH_STRING: case ENDS_WITH_STRING: return false; } case CONTAINS_STRING: switch (that.operator) { case EQUAL: return thatVal.contains(thisVal); case CONTAINS_STRING: case STARTS_WITH_STRING: case ENDS_WITH_STRING: return true; } case STARTS_WITH_STRING: switch (that.operator) { case EQUAL: case STARTS_WITH_STRING: return thatVal.startsWith(thisVal); case CONTAINS_STRING: case ENDS_WITH_STRING: return true; } case ENDS_WITH_STRING: switch (that.operator) { case EQUAL: case ENDS_WITH_STRING: return thatVal.endsWith(thisVal); case CONTAINS_STRING: case STARTS_WITH_STRING: return true; } default: System.err .println("Triplet.class: this shouldn't happen! (string)"); return false; } } else { // if not strings then they have to be numbers of some kind if (!(that.value instanceof Number)) { return false; } double thisVal, thatVal; Double[] thisArrayVal, thatArrayVal; // for every possible this numerical operator check that.operator switch (this.operator) { case EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: case GREATER_OR_EQUAL: thatVal = (Double) that.value; return thisVal >= thatVal; case GREATER_THAN: thatVal = (Double) that.value; return thisVal > thatVal; case LESS_OR_EQUAL: thatVal = (Double) that.value; return thisVal <= thatVal; case LESS_THAN: thatVal = (Double) that.value; return thisVal < thatVal; case BETWEEN: thatArrayVal = (Double[]) that.value; return (thisVal >= thatArrayVal[0] && thisVal <= thatArrayVal[1]); default: return false; } case GREATER_THAN: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return thisVal < thatVal; case GREATER_OR_EQUAL: case GREATER_THAN: return true; //both are not limited in the positive direction (i.e. + infinity) case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal < thatArrayVal[1]; case LESS_OR_EQUAL: case LESS_THAN: thatVal = (Double) that.value; return thisVal < thatVal; default: return false; } case GREATER_OR_EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return thisVal <= thatVal; case GREATER_OR_EQUAL: case GREATER_THAN: return true; //both are not limited in the positive direction (i.e. + infinity) case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal <= thatArrayVal[1]; case LESS_OR_EQUAL: thatVal = (Double) that.value; return (thisVal == thatVal || thisVal < thatVal); case LESS_THAN: thatVal = (Double) that.value; return thisVal < thatVal; default: return false; } case LESS_THAN: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return thisVal > thatVal; case LESS_OR_EQUAL: case LESS_THAN: return true; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal > thatArrayVal[0]; case GREATER_OR_EQUAL: case GREATER_THAN: thatVal = (Double) that.value; return thisVal > thatVal; default: return false; } case LESS_OR_EQUAL: thisVal = (Double) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return thisVal >= thatVal; case LESS_OR_EQUAL: case LESS_THAN: return true; case BETWEEN: thatArrayVal = (Double[]) that.value; return thisVal >= thatArrayVal[0]; case GREATER_OR_EQUAL: thatVal = (Double) that.value; return thisVal > thatVal; case GREATER_THAN: thatVal = (Double) that.value; return (thisVal == thatVal || thisVal > thatVal); default: return false; } case BETWEEN: thisArrayVal = (Double[]) this.value; switch (that.operator) { case EQUAL: thatVal = (Double) that.value; return (thisArrayVal[0] <= thatVal) && (thisArrayVal[1] >= thatVal); case BETWEEN: thatArrayVal = (Double[]) that.value; return (thisArrayVal[0] <= thatArrayVal[0]) || (thisArrayVal[1] >= thatArrayVal[1]); case LESS_OR_EQUAL: thatVal = (Double) that.value; return thisArrayVal[0] <= thatVal; case LESS_THAN: thatVal = (Double) that.value; return thisArrayVal[0] < thatVal; case GREATER_OR_EQUAL: thatVal = (Double) that.value; return thisArrayVal[1] >= thatVal; case GREATER_THAN: thatVal = (Double) that.value; return thisArrayVal[1] > thatVal; default: return false; } default: System.err .println("Triplet.class: this shouldn't happen! (numeric)"); return false; } } } /** * Help method that checks if this triplet covers a given string value. This * is the same as calling the Triplet.covers method on a triplet that has * the same key as this triplet, the same value as the one passed here, and * the operator is EQUALS. * */ public boolean covers(String thatVal) { if (!(this.value instanceof String)) { return false; } String thisVal = ((String) this.value).toLowerCase(Locale.ENGLISH); thatVal = thatVal.toLowerCase(Locale.ENGLISH); // for every possible this string operator switch (this.operator) { case EQUAL: return thisVal.equals(thatVal); case CONTAINS_STRING: return thatVal.contains(thisVal); case STARTS_WITH_STRING: return thatVal.startsWith(thisVal); case ENDS_WITH_STRING: return thatVal.endsWith(thisVal); default: System.err .println("Triplet.class: this shouldn't happen! (string_2)"); return false; } } /** * Help method that checks if this triplet covers a given double value. This * is the same as calling the Triplet.covers method on a triplet that has * the same key as this triplet, the same value as the one passed here, and * the operator is EQUALS. * */ public boolean covers(double thatVal) { Double thisVal = null; Double[] thisArray = null; if (this.value instanceof Double) { thisVal = (Double) this.value; } else if (this.value instanceof Double[]) { thisArray = (Double[]) this.value; } else { return false; } // for every possible this numerical operator switch (this.operator) { case EQUAL: return thisVal == thatVal; case GREATER_THAN: return thisVal < thatVal; case GREATER_OR_EQUAL: return thisVal <= thatVal; case LESS_THAN: return thisVal > thatVal; case LESS_OR_EQUAL: return thisVal >= thatVal; case BETWEEN: return (thisArray[0] <= thatVal) && (thatVal <= thisArray[1]); default: System.err .println("Triplet.class: this shouldn't happen! (numeric_2)"); return false; } } /** * Tests if the two Triplets are equivalent (disregarding their exact * parameters). Two Triplets are equivalent when they cover each other. */ public boolean isEquivalent(Triplet other) { return (this.covers(other) && other.covers(this)); } /** * Two Triplets are equal if the have the exact same key, value and * operator. Two Triplets can be EQUIVALENT without being EQUAL, because a * different combination of values and operators can yield the same results. * For example (BETWEEN [-inf, 5]) and (LESS_OR_EQUAL 5). For testing * equivalence use the isEquivalent method! */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (other instanceof Triplet) { Triplet otherTriplet = (Triplet) other; return (this.key.equals(otherTriplet.key) && this.value.equals(otherTriplet.value) && this.operator .equals(otherTriplet.operator)); } else { return false; } } @Override public int hashCode() { int hash = 7; hash = 11 * hash + (this.key != null ? this.key.hashCode() : 0); hash = 11 * hash + (this.value != null ? this.value.hashCode() : 0); hash = 11 * hash + (this.operator != null ? this.operator.hashCode() : 0); return hash; } @Override public String toString() { if (value instanceof Double[]) { Double[] val = (Double[]) this.value; return (this.key + " " + this.operator + " [" + val[0] + "," + val[1] + "]"); } else { return (this.key + " " + this.operator + " " + this.value); } } }