/*
* This file is part of INDI for Java Client.
*
* INDI for Java Client 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 3 of
* the License, or (at your option) any later version.
*
* INDI for Java Client 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 INDI for Java Client. If not, see
* <http://www.gnu.org/licenses/>.
*/
package laazotea.indi.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import laazotea.indi.Constants;
import laazotea.indi.Constants.PropertyPermissions;
import laazotea.indi.Constants.PropertyStates;
import laazotea.indi.INDIException;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import android.util.Log;
/**
* A class representing a INDI Property. The subclasses
* <code>INDIBLOBProperty</code>,
* <code>INDILightProperty</code>,
* <code>INDINumberProperty</code>,
* <code>INDISwitchProperty</code> and
* <code>INDITextProperty</code> define the basic Properties that a INDI Devices
* may contain according to the INDI protocol. <p> It implements a listener
* mechanism to notify changes in its Elements.
*
* @author S. Alonso (Zerjillo) [zerjioi at ugr.es]
* @version 1.32, January 27, 2013
*/
public abstract class INDIProperty {
/**
* The INDI Device to which this property belongs
*/
private INDIDevice device;
/**
* This property name
*/
private String name;
/**
* This property label
*/
private String label;
/**
* The group to which this Property might be assigned
*/
private String group;
/**
* The current state of this Property
*/
private PropertyStates state;
/**
* The permission of this Property
*/
private PropertyPermissions permission;
/**
* The timeout of this Property
*/
private int timeout;
/**
* A list of elements for this Property
*/
private LinkedHashMap<String, INDIElement> elements;
/**
* The list of listeners of this Property
*/
private ArrayList<INDIPropertyListener> listeners;
/**
* Constructs an instance of
* <code>INDIProperty</code>. Called by its sub-classes.
* <code>INDIProperty</code>s are not usually directly instantiated. Usually
* used by
* <code>INDIDevice</code>.
*
* @param xml A XML Element
* <code><defXXXVector></code> describing the Property.
* @param device The
* <code>INDIDevice</code> to which this Property belongs.
* @throws IllegalArgumentException if the XML Property is not well formed
* (does not contain a
* <code>name</code> attribute or the permissions are not correct).
*/
protected INDIProperty(Element xml, INDIDevice device) throws IllegalArgumentException {
this.device = device;
name = xml.getAttribute("name").trim();
if (name.compareTo("") == 0) { // If no name, ignore
throw new IllegalArgumentException("No name for the Property");
}
label = xml.getAttribute("label").trim();
if (label.compareTo("") == 0) { // If no label copy from name
this.label = name;
}
group = xml.getAttribute("group").trim();
if (group.compareTo("") == 0) { // If no group, create default group
group = "Unsorted";
}
String sta = xml.getAttribute("state").trim();
setState(sta);
if ((this instanceof INDITextProperty) || (this instanceof INDINumberProperty) || (this instanceof INDISwitchProperty) || (this instanceof INDIBLOBProperty)) {
String per = xml.getAttribute("perm").trim();
permission = Constants.parsePropertyPermission(per);
String to = xml.getAttribute("timeout").trim();
if (!(to.length() == 0)) {
setTimeout(to);
} else {
timeout = 0;
}
}
if (this.getClass() == INDILightProperty.class) {
timeout = 0;
permission = PropertyPermissions.RO;
}
this.elements = new LinkedHashMap<String, INDIElement>();
this.listeners = new ArrayList<INDIPropertyListener>();
}
/**
* Updates the values of its elements according to some XML data. Subclasses
* of
* <code>INDIProperty</code> must implement this method to really do the
* parsing and update (usually calling
* <code>update(Element, String)</code>).
*
* @param xml A XML Element
* <code><setXXXVector></code> to which the property must be updated.
* @see #update(Element, String)
*/
protected abstract void update(Element xml);
/**
* Updates the values of its elements according to some XML data. Subclasses
* of
* <code>INDIProperty</code> usually call this method from
* <code>update(Element)</code> to really do the parsing and update.
*
* @param xml A XML Element
* <code><setXXXVector></code> to which the property must be updated.
* @param childNodesType The real XML type of
* <code>xml</code>, that is, one of
* <code><setBLOBVector></code>,
* <code><setLightVector></code>,
* <code><setNumberVector></code>,
* <code><setSwitchVector></code> or
* <code><setTextVector></code>.
* @see #update(Element)
*/
protected void update(Element xml, String childNodesType) {
try {
String sta = xml.getAttribute("state").trim();
if (!(sta.length() == 0)) {
setState(sta);
}
String to = xml.getAttribute("timeout").trim();
if (!(to.length() == 0)) {
setTimeout(to);
}
NodeList list = xml.getElementsByTagName(childNodesType);
for (int i = 0; i < list.getLength(); i++) {
Element child = (Element) list.item(i);
String ename = child.getAttribute("name");
INDIElement iel = getElement(ename);
// System.out.println(child + " - " + name + " - " + iel);
if (iel != null) { // It already exists else ignore
iel.setValue(child);
}
}
} catch (IllegalArgumentException e) { // If there was some poblem parsing then set to alert
state = PropertyStates.ALERT;
}
notifyListeners();
}
/**
* Sets the state of this property.
*
* @param newState The new state for this Property in form of a String:
* <code>Idle</code>,
* <code>Ok</code>,
* <code>Busy</code> or
* <code>Alert</code>.
* @throws IllegalArgumentException if the
* <code>newState</code> is not a valid one.
*/
private void setState(String newState) throws IllegalArgumentException {
state = Constants.parsePropertyState(newState);
}
/**
* Sets the current timeout for this Property
*
* @param newTimeout The new current timeout.
* @throws IllegalArgumentException if the format of the timeout is not
* correct (a positive integer).
*/
private void setTimeout(String newTimeout) throws IllegalArgumentException {
try {
timeout = Integer.parseInt(newTimeout);
if (timeout < 0) {
throw new IllegalArgumentException("Illegal timeout for the Property");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Illegal timeout for the Property");
}
}
/**
* Gets the Device that owns this Property.
*
* @return the Device that owns this Property
*/
public INDIDevice getDevice() {
return device;
}
/**
* Gets the Group to which this property might be assigned.
*
* @return the group to which this property might be assigned.
*/
public String getGroup() {
return group;
}
/**
* Gets the timeout for this Property.
*
* @return the timeout for this Property.
*/
public int getTimeout() {
return timeout;
}
/**
* Gets the label for this Property.
*
* @return the label for this Property.
*/
public String getLabel() {
return label;
}
/**
* Gets the name of this Property
*
* @return the name of this Property
*/
public String getName() {
return name;
}
/**
* Gets the number of Elements in this Property
*
* @return the number of Elements in this Property.
*/
public int getElementCount() {
return elements.size();
}
/**
* Gets the Permission of this Property.
*
* @return the Permission of this Property.
*/
public PropertyPermissions getPermission() {
return permission;
}
/**
* Gets the State of this Property.
*
* @return the State of this Property.
*/
public PropertyStates getState() {
return state;
}
/**
* Sets the timeout of this property.
*
* @param timeout the new timeout for this Property.
*/
protected void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* Sets the State of this Property. The listeners are notified of this change.
*
* @param state the new State of this Property.
*/
protected void setState(PropertyStates state) {
this.state = state;
notifyListeners();
}
/**
* Sets the Permission of this Property.
*
* @param permission the new Permission for this Property.
*/
protected void setPermission(PropertyPermissions permission) {
this.permission = permission;
}
/**
* Adds a new Element to this Property.
*
* @param element the Element to be added.
*/
protected void addElement(INDIElement element) {
elements.put(element.getName(), element);
}
/**
* Gets a particular Element of this Property by its name.
*
* @param name The name of the Element to be returned
* @return The Element of this Property with the given
* <code>name</code>.
* <code>null</code> if there is no Element with that
* <code>name</code>.
*/
public INDIElement getElement(String name) {
return elements.get(name);
}
/**
* Gets a
* <code>ArrayList</code> with all the Elements of this Property.
*
* @return the
* <code>ArrayList</code> of Elements belonging to this Property.
*/
public ArrayList<INDIElement> getElementsAsList() {
return new ArrayList<INDIElement>(elements.values());
}
/**
* Tests and changes the desired values of the the Elements of this Property.
* If there are new desired values for any Elements the XML code to produce
* the change is sent to the INDI Driver. If communication is successful the
* state of the property is set to "Busy".
*
* @throws INDIValueException if some of the desired values are not correct or
* if the Property is Read Only.
* @throws IOException if there is some communication problem with the INDI
* driver connection.
*/
public void sendChangesToDriver() throws INDIValueException, IOException {
if (permission == PropertyPermissions.RO) {
throw new INDIValueException(null, "The property is read only");
}
ArrayList<INDIElement> elems = getElementsAsList();
int changedElements = 0;
String xml = "";
for (int i = 0; i < elems.size(); i++) {
INDIElement el = elems.get(i);
if (el.isChanged()) {
changedElements++;
xml += el.getXMLOneElementNewValue();
}
}
if (changedElements > 0) {
setState(PropertyStates.BUSY);
xml = getXMLPropertyChangeInit() + xml + getXMLPropertyChangeEnd();
Log.w("INDI",xml);
device.sendMessageToServer(xml);
}
}
/**
* Adds a new listener to this Property.
*
* @param listener the listener to be added.
*/
public void addINDIPropertyListener(INDIPropertyListener listener) {
listeners.add(listener);
}
/**
* Removes a listener from this Property.
*
* @param listener the listener to be removed.
*/
public void removeINDIPropertyListener(INDIPropertyListener listener) {
listeners.remove(listener);
}
/**
* Notifies all the listeners about the changes in the Property.
*/
private void notifyListeners() {
ArrayList<INDIPropertyListener> lCopy = (ArrayList<INDIPropertyListener>)listeners.clone();
for (int i = 0; i < lCopy.size(); i++) {
INDIPropertyListener l = lCopy.get(i);
l.propertyChanged(this);
}
}
/**
* Gets the opening XML Element <newXXXVector> for this Property.
*
* @return the opening XML Element <newXXXVector> for this Property.
*/
protected abstract String getXMLPropertyChangeInit();
/**
* Gets the closing XML Element </newXXXVector> for this Property.
*
* @return the closing XML Element </newXXXVector> for this Property.
*/
protected abstract String getXMLPropertyChangeEnd();
/**
* Gets a default UI component to handle the repesentation and control of this
* Property. The panel is registered as a listener of this Property. Please
* note that the UI class must implement INDIPropertyListener. The component
* will be chosen depending on the loaded UI libraries (I4JClientUI,
* I4JAndroid, etc). Note that a casting of the returned value must be done.
*
* If a previous default component has been requested, the previous one will
* be deregistered. So, only one default component will listen for the
* property.
*
* @return A UI component that handles this Property.
*/
public abstract INDIPropertyListener getDefaultUIComponent() throws INDIException;
/**
* Gets the names of the Elements of this Property.
*
* @return the names of the Elements of this Property.
*/
public String[] getElementNames() {
List<INDIElement> l = getElementsAsList();
String[] names = new String[l.size()];
for (int i = 0; i < l.size(); i++) {
names[i] = l.get(i).getName();
}
return names;
}
/**
* Returns a String with the name of the Property, its state and its elements
* and values.
*
* @return a String representation of the property and its values.
*/
public String getNameStateAndValuesAsString() {
String aux = getName() + " - " + getState() + "\n";
List<INDIElement> l = getElementsAsList();
for (int i = 0; i < l.size(); i++) {
aux += " " + l.get(i).getNameAndValueAsString() + "\n";
}
return aux;
}
/**
* Gets the values of the Property as a String.
*
* @return A String representation of the value of the Property.
*/
public String getValuesAsString() {
String aux = "[";
ArrayList<INDIElement> l = this.getElementsAsList();
for (int i = 0; i < l.size(); i++) {
if (i == 0) {
aux += l.get(i).toString();
} else {
aux += ", " + l.get(i).toString();
}
}
return aux + "]";
}
}