/**
*
* Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* Freedomotic; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
//Copyright 2009 Enrico Nicoletti
//eMail: enrico.nicoletti84@gmail.com
//
//This file is part of EventEngine.
//
//EventEngine is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//any later version.
//
//EventEngine 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.
//
//You should have received a copy of the GNU General Public License
//along with EventEngine; if not, write to the Free Software
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.freedomotic.reactions;
import com.freedomotic.model.ds.Config;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import org.slf4j.LoggerFactory;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import org.slf4j.Logger;
/**
*
* @author Enrico Nicoletti
*/
@XmlRootElement
public final class Command implements Serializable, Cloneable {
private static final long serialVersionUID = -7287958816826580426L;
private static final Logger LOG = LoggerFactory.getLogger(Command.class.getName());
public static final String PROPERTY_BEHAVIOR = "behavior";
public static final String PROPERTY_OBJECT_CLASS = "object.class";
public static final String PROPERTY_OBJECT_ADDRESS = "object.address";
public static final String PROPERTY_OBJECT_NAME = "object.name";
public static final String PROPERTY_OBJECT_PROTOCOL = "object.protocol";
public static final String PROPERTY_OBJECT_INCLUDETAGS = "object.includetags";
public static final String PROPERTY_OBJECT_EXCLUDETAGS = "object.excludetags";
public static final String PROPERTY_OBJECT_ENVIRONMENT = "object.environment";
public static final String PROPERTY_OBJECT_ZONE = "object.zone";
public static final String PROPERTY_OBJECT = "object";
private String name;
private String receiver;
private String uuid;
private int delay;
private int timeout;
private String description;
private String stopIf;
private HashSet<String> tags = new HashSet<>();
//by default a command is userLevel, this means that can be used in reactions.
//Hardware level commands cannot be used in reactions but only linked to an object action
private boolean hardwareLevel;
private boolean editable;
private boolean executed;
@XmlElement(name = "props")
private Config properties = new Config();
private final String type = "command";
/**
*
*/
public Command() {
this.uuid = UUID.randomUUID().toString();
this.properties.setProperty("type", type);
if (isHardwareLevel()) { //an hardware level command
setEditable(false); //it has not to be stored in root/data folder
}
}
/**
* Gets the tags associated to the command.
*
* @return a set of tags
*/
public Set<String> getTags() {
if (tags == null) {
tags = new HashSet<>();
tags.addAll(Arrays.asList(getName().toLowerCase().split(" ")));
}
return tags;
}
/**
* Sets the tags associated to the command.
*
* @param tags the tags to associate
*/
public void setTags(HashSet<String> tags) {
this.tags = tags;
}
/**
* Returns a command description.
*
* @return the string description
*/
public String getDescription() {
if (description == null) {
description = getName();
}
return description;
}
/**
* Sets a command description.
*
* @param description the command description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Gets the command uuid.
*
* @return the command uuid
*/
public String getUuid() {
if (uuid == null || "".equals(uuid.trim())) {
uuid = UUID.randomUUID().toString();
}
return uuid;
}
/**
* Sets the command uuid.
*
* @param uuid the command uuid to set
*/
public void setUUID(String uuid) {
this.uuid = uuid;
}
/**
* Checks it is a hardware level command. This type of command can't be used
* in reactions but only linked to an object action
*
* @return true if it's a hardware level command, false otherwise
*/
public boolean isHardwareLevel() {
return hardwareLevel;
}
/**
* Sets the command as hardware level one or not.
*
* @param hardwareLevel
*/
public void setHardwareLevel(boolean hardwareLevel) {
this.hardwareLevel = hardwareLevel;
}
/**
* Checks if the command is persisted.
*
* @return true if the command is persisted, false otherwise
*/
public boolean isEditable() {
return editable;
}
/**
* Sets the command persistence.
*
* @param persistence
*/
public void setEditable(boolean persistence) {
this.editable = persistence;
}
/**
*
* @return
*/
public String getStopIf() {
return stopIf;
}
/**
*
* @param continueIf
*/
public void setStopIf(String continueIf) {
this.stopIf = continueIf;
}
/**
* Gets a "behavior" property.
*
* @return the string property
*/
@XmlTransient
public String getBehavior() {
if (properties.getProperty("behavior") != null) {
return properties.getProperty("behavior");
} else {
LOG.warn("Undefined property 'behavior' in command '" + this.getName() + "'");
return "undefined-behavior";
}
}
/**
* Gets the value of a property.
*
* @param key the property key
* @return the value of the key or null if not found
*/
public String getProperty(String key) {
return properties.getProperty(key);
}
/**
* Gets a boolean property with a fallback default value.
*
* @param key the string key
* @param defaultValue the value to use if the given key does not exists
* @return the property value or the default value if the key doesn't exists
*/
public boolean getBooleanProperty(String key, boolean defaultValue) {
String result = properties.getProperty(key);
if (result != null) {
if ("true".equalsIgnoreCase(result.trim())) {
return true;
} else {
if ("false".equalsIgnoreCase(result.trim())) {
return false;
}
}
}
return defaultValue;
}
/**
* Sets a property value.
*
* @param key the string key (empty or null values not allowed)
* @param value the value associated to the property (empty or null values not allowed)
*/
public void setProperty(String key, String value) {
if (key == null || key.isEmpty() || value == null || value.isEmpty()) {
throw new IllegalArgumentException("Cannot add empty or null properties "
+ "[" + key + " = " + value + "] in command '" + this.getName() + "'");
} else {
properties.setProperty(key, value);
}
}
/**
* Gets the command properties.
*
* @return the command properties
*/
public Config getProperties() {
return properties;
}
/**
* Creates an ordered list reading the command properties written in the format
* "parameter[AN_INT_FROM_0_TO_999]". Other properties formats are ignored
* (not added to the returned List.) The indexes must be contiguous
* (1,2,3,...). For example:
*
* <p>
* <li>parameter[0] = foo<li> <li>parameter[1] = bar</li>
* <li>parameter[3] = asd</li> <li>object = Light 1</li> <li>another-param =
* myValue</li> </p>
*
* <p>
* The returned ArrayList<String> is <li>[0]->foo</li> <li>[1]->bar</li>
* because the index = 2 is missing. </p>
*
* @return an ordered ArrayList<String> of command parameter values
*/
@XmlTransient
public List<String> getParametersAsList() {
ArrayList<String> output = new ArrayList<>();
//99 is the max num of elements in list
for (int i = 0; i < 99; i++) {
String value = null;
value = properties.getProperties().getProperty("parameter[" + i + "]");
if (value != null) {
//add to array
output.add(value);
} else {
//if the elements are not contiguous return
return output;
}
}
return output;
}
/**
* Gets the command delay.
*
* @return the command delay
*/
public int getDelay() {
if (delay > 0) {
return delay;
} else {
return 0;
}
}
/**
* Sets the command delay.
*
* @param delay the delay to set
*/
public void setDelay(int delay) {
this.delay = delay;
}
/**
* Gets the command name.
*
* @return the command name
*/
public String getName() {
return name;
}
/**
* Sets the command name.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name.trim();
}
/**
* Gets the channel the command listens to.
*
* @return the channel the command listens to
*/
public String getReceiver() {
return receiver;
}
/**
* Sets the channel the command listens to.
*
* @param receiver the channel to set
*/
public void setReceiver(String receiver) {
this.receiver = receiver;
}
/**
* Sets the command as "executed".
*
* @param value the "executed" value
*/
public void setExecuted(boolean value) {
executed = value;
}
/**
* Gets the command "executed" state.
*
* @return true if the command has been executed, false otherwise
*/
public boolean isExecuted() {
return executed;
}
/**
* Two commands are considered equals if they have the same name.
*
* @param obj the object to compare
* @return true it the two commands are equal, false otherwise
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Command other = (Command) obj;
if ((this.name == null) ? (other.name != null) : (!this.name.equals(other.name))) {
return false;
}
return true;
}
/**
*
* @return
*/
@Override
public int hashCode() {
int hash = 5;
hash = (53 * hash) + ((this.name != null) ? this.name.hashCode() : 0);
return hash;
}
/**
* Creates a clone of the command.
*
* @return a clone of the command
* @throws CloneNotSupportedException
*/
@Override
public Command clone()
throws CloneNotSupportedException {
super.clone();
Command clonedCmd = new Command();
clonedCmd.setName(getName());
clonedCmd.setDescription(getDescription());
clonedCmd.setReceiver(getReceiver());
clonedCmd.setDelay(getDelay());
clonedCmd.setReplyTimeout(getReplyTimeout());
clonedCmd.setExecuted(executed);
clonedCmd.setHardwareLevel(hardwareLevel);
Iterator<Entry<Object, Object>> it = getProperties().entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Object> e = it.next();
clonedCmd.setProperty(e.getKey().toString(), e.getValue().toString()); //adding the original command properties to its clone
}
clonedCmd.properties.setXmlFile(this.getName());
return clonedCmd;
}
/**
* Cleares all variables.
*
*/
public void destroy() {
name = null;
receiver = null;
description = null;
stopIf = null;
properties.getProperties().clear();
properties = null;
}
/**
*
*
* @return
*/
@Override
public String toString() {
return getName();
}
/**
* Gets the command reply timeout.
*
* @return the command reply timeout
*/
public int getReplyTimeout() {
return timeout;
}
/**
* Sets the command reply timeout.
*
* @param timeout the timeout to set
*/
public void setReplyTimeout(int timeout) {
this.timeout = timeout;
}
/**
* @return the type
*/
public String getType() {
return type;
}
}