/**
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import org.openiot.cupus.artefact.HashtablePublication;
import org.openiot.cupus.artefact.Publication;
import org.openiot.cupus.artefact.Subscription;
import org.openiot.cupus.artefact.TripletSubscription;
import org.openiot.cupus.common.enums.Operator;
public class Attributes implements Serializable {
static final long serialVersionUID = 1L;
private static Attributes instance = null;
public static Attributes getInstance() {
if (Attributes.instance == null) {
Attributes.instance = new Attributes();
}
return instance;
}
/**
* Sets the singleton instance if it is null (otherwise it doesn't)
*
* @return true if set, false if not
*/
public static boolean setInstance(Attributes params) {
if (Attributes.instance == null) {
Attributes.instance = params;
return true;
} else {
return false;
}
}
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private HashMap<String, Attribute> hashMap;
public Attributes() {
hashMap = new HashMap<String, Attribute>();
}
/**
* This method loads the attributes from a textual stream (for example from
* a file stream).<br>
* The textual representation has to obey some rules:
* <ol>
* <li>Each attribute is written in only one line</li>
* <li>Numeric attributes are given in the form:
* attrName#minValue:maxValue:step</li>
* <li>String attributes are given in the form:
* attrName$value1:value2:...:valueN</li>
* <li>Empty lines are allowed and will be ignored</li>
* <li>All lines starting with "%" will be treated as comments and ignored</li>
* <li>EVERYTHING ELSE IS NOT ALLOWED AND WILL CAUSE LOAD TO THROW AN
* EXCEPTION!</li>
* </ol>
*
* @param in
* @throws java.io.IOException
*/
public void load(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
int lineCounter = 0;
while ((line = reader.readLine()) != null) {
lineCounter++;
line = line.trim();
if (line.equals("") || line.charAt(0) == '%') // ignore comments and
// empty lines
continue;
int attrNameEnd = -1;
String attrName = null;
if ((attrNameEnd = line.indexOf('#')) > 0) { // numeric attribute
attrName = line.substring(0, attrNameEnd);
String[] parts = line.substring(attrNameEnd + 1).split(":");
NumericAttribute numAttr = new NumericAttribute(attrName,
Double.parseDouble(parts[0]),
Double.parseDouble(parts[1]),
Double.parseDouble(parts[2]));
this.addAttribute(attrName, numAttr);
} else if ((attrNameEnd = line.indexOf('$')) > 0) { // string
// attribute
attrName = line.substring(0, attrNameEnd);
String[] parts = line.substring(attrNameEnd + 1).split(":");
List<String> values = new ArrayList<String>(parts.length);
for (String s : parts) { // everything to lowercase
if (s.equals(""))
continue;
values.add(s.toLowerCase(Locale.ENGLISH));
}
if (values.size() == 0)
throw new IOException("Attribute " + attrName
+ " defined in line " + lineCounter
+ " has no values defined!");
StringAttribute strAttr = new StringAttribute(attrName, values);
this.addAttribute(attrName, strAttr);
} else {
throw new IOException("Line " + lineCounter
+ " has wrong syntax! Neither '#' nor '$' present.");
}
}
}
/**
* Tries to add a new attribute. If there already is an attribute with the
* same name then it does nothing and returns false.
*/
public boolean addAttribute(String attrName, Attribute value) {
if (hashMap.containsKey(attrName)) {
return false;
} else {
hashMap.put(attrName, value);
return true;
}
}
/**
* Returns the Attribute object associated with this attribute name, or NULL
* if there is no suck Attribute object.
*/
public Attribute getAttribute(String attrName) {
return hashMap.get(attrName);
}
public Set<String> attributeSet() {
return hashMap.keySet();
}
/**
* Method for checking if a received publication contains only the allowed
* attributes for this pub/sub and if their values are in the allowed range.
*
* @return true if OK, false if not. Also true if there are no defined
* attributes!
*/
public boolean checkPublication(Publication publication) {
if (hashMap.isEmpty()) // in case there are no defined attributes
// (forest case)
return true;
if (publication instanceof HashtablePublication) {
HashtablePublication pub = (HashtablePublication) publication;
for (Entry<String, Object> entry : pub.getProperties().entrySet()) {
Attribute attr = hashMap.get(entry.getKey());
if (attr == null) {
return false; // no attribute with that name/key
// registered...
} else if (attr instanceof StringAttribute) {
if (!(entry.getValue() instanceof String)) {
System.err.println("Type mismatch, value of attribute "
+ entry.getKey() + " is not a string!");
return false;
}
if (!((StringAttribute) attr).getValues().contains(
((String) entry.getValue())
.toLowerCase(Locale.ENGLISH)))
return false;
} else if (attr instanceof NumericAttribute) {
NumericAttribute numAttr = (NumericAttribute) attr;
double value;
try {
value = Double.parseDouble(entry.getValue().toString());
} catch (NumberFormatException e) {
System.err.println("Type mismatch, value of attribute "
+ entry.getKey() + " is not numeric!");
return false;
}
if (value < numAttr.getLowerBound()
|| value > numAttr.getUpperBound()) {
return false;
} else {
// rounding value to nearest step of attribute
value = (value - numAttr.getLowerBound());
value = (double) Math.round(value / numAttr.getStep());
value = numAttr.getLowerBound() + value
* numAttr.getStep();
// setting the rounded value in the publication
entry.setValue(value);
}
} else {
System.err
.println("Unknown Attribute type (Attributes.checkPublication).");
return false; // should not happen
}
}
return true; // if all values passed the check...
} else {
return false;
}
}
/**
* Method for checking if a received subscription contains only the allowed
* attributes for this pub/sub and if their ranges are in the allowed range
* of each attribute.
*
* @return true if OK, false if not. Also true if there are no defined
* attributes!
*/
public boolean checkSubscription(Subscription subscription) {
if (hashMap.isEmpty()) // in case there are no defined attributes
// (forest case)
return true;
if (subscription instanceof TripletSubscription) {
TripletSubscription sub = (TripletSubscription) subscription;
sub.stringAttributeBorders = null; // just in case
for (String attribute : sub.attributes()) {
Attribute attr = hashMap.get(attribute);
if (attr == null) {
return false; // no attribute with that name/key
// registered...
} else if (attr instanceof StringAttribute) {
if (!checkStringAttribute((StringAttribute) attr, sub))
return false;
} else if (attr instanceof NumericAttribute) {
if (!checkNumAttribute((NumericAttribute) attr, sub))
return false;
} else {
System.err
.println("Unknown Attribute type (Attributes.checkPublication).");
return false; // should not happen
}
}
return true; // if all constraints passed the check...
} else {
return false;
}
}
private boolean checkStringAttribute(StringAttribute attr,
TripletSubscription sub) {
Set<Triplet> attrPreds = sub.attributePredicates(attr.getName());
for (Triplet triplet : attrPreds) {
if (!(triplet.getValue() instanceof String)) {
System.err.println("Type mismatch, value of attribute "
+ attr.getName() + " is not a string!");
return false;
}
}
// atleast one legal value has to be found that satisfies all the
// constraints at the same time!
int numValues = attr.getValues().size();
int strIndexMin = numValues - 1, strIndexMax = 0;
for (int i = 0; i < numValues; i++) {
String legalValue = attr.getValues().get(i);
boolean satisfies = true;
for (Triplet triplet : attrPreds) {
if (!triplet.covers(legalValue)) {
satisfies = false; // as soon as one constraint isn't
// satisfied go on to next string
break;
}
}
if (satisfies) {
if (i < strIndexMin)
strIndexMin = i;
if (i > strIndexMax)
strIndexMax = i;
}
}
if (strIndexMin == numValues - 1 && strIndexMax == 0) {
return false; // if none were found these are going to be
// unchanged...
} else {
if (sub.stringAttributeBorders == null) {
sub.stringAttributeBorders = new HashMap<String, Triplet>();
}
sub.stringAttributeBorders.put(attr.getName(),
new Triplet(attr.getName(), new Integer[] { strIndexMin,
strIndexMax }, Operator.BETWEEN));
return true;
}
}
private boolean checkNumAttribute(NumericAttribute attr,
TripletSubscription sub) {
// FIXME assumption here that the constraints (predicates) will be
// consistent,
// meaning no impossible combinations like (x<5 && x>7) or (x>2 && x<5
// && x==7)
for (Triplet triplet : sub.attributePredicates(attr.getName())) {
Object value = triplet.getValue();
// the value has to be inside the range, regardless of the
// operator...
// example. if range=[0,10] then if (x>=7) range=[7,10]
if (value instanceof Double[]) {
Double[] numVals = (Double[]) value;
if (numVals[0] > attr.getUpperBound()
|| numVals[1] < attr.getLowerBound()) {
return false;
} else {
// rounding values to nearest step of attribute
for (int i = 0; i < 2; i++) {
numVals[i] = (numVals[i] - attr.getLowerBound());
numVals[i] = (double) Math.round(numVals[i]
/ attr.getStep());
numVals[i] = attr.getLowerBound() + numVals[i]
* attr.getStep();
// the rounded values are set in the subscription by
// reference
}
}
} else if (value instanceof Double) {
Double numVal = (Double) value;
if (numVal < attr.getLowerBound()
|| numVal > attr.getUpperBound()) {
return false;
} else {
// rounding value to nearest step of attribute
numVal = (numVal - attr.getLowerBound());
numVal = (double) Math.round(numVal / attr.getStep());
numVal = attr.getLowerBound() + numVal * attr.getStep();
// setting the rounded value in the subscription
triplet.setValue(numVal);
}
} else {
System.err.println("Type mismatch, value of attribute "
+ attr.getName() + " is not numeric!");
return false;
}
}
return true;
}
public int numberOfAttributes() {
return hashMap.size();
}
public boolean isEmpty() {
return (hashMap.size() == 0);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Attributes:\n");
for (Attribute value : hashMap.values()) {
sb.append(value + "\n");
}
return sb.toString();
}
}