/*
* 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.*;
import laazotea.indi.ClassInstantiator;
import laazotea.indi.Constants;
import laazotea.indi.Constants.BLOBEnables;
import laazotea.indi.INDIDateFormat;
import laazotea.indi.INDIException;
import org.w3c.dom.Element;
/**
* A class representing a INDI Device.
*
* @author S. Alonso (Zerjillo) [zerjioi at ugr.es]
* @version 1.32, January 27, 2013
*/
public class INDIDevice {
/**
* The name of the Device.
*/
private String name;
/**
* The Server Connection to which this Device Belongs
*/
private INDIServerConnection server;
/**
* The collection of properties for this Device
*/
private LinkedHashMap<String, INDIProperty> properties;
/**
* The list of Listeners of this Device.
*/
private ArrayList<INDIDeviceListener> listeners;
/**
* A UI component that can be used in graphical interfaces for this Device.
*/
private INDIDeviceListener UIComponent;
/**
* The timestamp for the last message.
*/
private Date timestamp;
/**
* The last message of this Device.
*/
private String message;
/**
* The number of
* <code>BLOBProperties</code> in this Device.
*/
private int blobCount;
/**
* Constructs an instance of
* <code>INDIDevice</code>. Usually called from a
* <code>INDIServerConnection</code>.
*
* @param name the name of this Device
* @param server the Server Connection of this Device
* @see INDIServerConnection
*/
protected INDIDevice(String name, INDIServerConnection server) {
this.name = name;
this.server = server;
this.properties = new LinkedHashMap<String, INDIProperty>();
this.listeners = new ArrayList<INDIDeviceListener>();
timestamp = new Date();
message = "";
blobCount = 0;
}
/**
* Adds a new listener to this Device.
*
* @param listener the listener to be added.
*/
public void addINDIDeviceListener(INDIDeviceListener listener) {
listeners.add(listener);
}
/**
* Removes a listener from this Device.
*
* @param listener the listener to be removed.
*/
public void removeINDIDeviceListener(INDIDeviceListener listener) {
listeners.remove(listener);
}
/**
* Notifies the listeners of a new
* <code>INDIProperty</code>
*
* @param property The new property
*/
private void notifyListenersNewProperty(INDIProperty property) {
ArrayList<INDIDeviceListener> lCopy = (ArrayList<INDIDeviceListener>) listeners.clone();
for (int i = 0; i < lCopy.size(); i++) {
INDIDeviceListener l = lCopy.get(i);
l.newProperty(this, property);
}
}
/**
* Notifies the listeners of a removed
* <code>INDIProperty</code>
*
* @param property The removed property
*/
private void notifyListenersDeleteProperty(INDIProperty property) {
ArrayList<INDIDeviceListener> lCopy = (ArrayList<INDIDeviceListener>) listeners.clone();
for (int i = 0; i < lCopy.size(); i++) {
INDIDeviceListener l = lCopy.get(i);
l.removeProperty(this, property);
}
}
/**
* Notifies the listeners that the message of this Device has changed.
*/
private void notifyListenersMessageChanged() {
ArrayList<INDIDeviceListener> lCopy = (ArrayList<INDIDeviceListener>) listeners.clone();
for (int i = 0; i < lCopy.size(); i++) {
INDIDeviceListener l = lCopy.get(i);
l.messageChanged(this);
}
}
/**
* Sends the appropriate message to the Server to establish a particular BLOB
* policy (BLOBEnable) for the Device.
*
* @param enable The BLOB policy.
* @throws IOException if there is some problem sending the message.
*/
public void BLOBsEnable(BLOBEnables enable) throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\">" + Constants.getBLOBEnableAsString(enable) + "</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to establish a particular BLOB
* policy (BLOBEnable) for the Device and a particular Property.
*
* @param enable The BLOB policy.
* @param property The Property of the Device to listen to.
* @throws IOException if there is some problem sending the message.
*/
public void BLOBsEnable(BLOBEnables enable, INDIProperty property) throws IOException {
if ((properties.containsValue(property)) && (property instanceof INDIBLOBProperty)) {
String xml = "<enableBLOB device=\"" + getName() + "\" name=\"" + property.getName() + "\">" + Constants.getBLOBEnableAsString(enable) + "</enableBLOB>";
sendMessageToServer(xml);
}
}
/**
* Sends the appropriate message to the Server to disallow the receipt of BLOB
* property changes.
*
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables)}
*/
@Deprecated
public void BLOBsEnableNever() throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\">Never</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to allow the receipt of BLOB
* property changes along with any other property types.
*
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables)}
*/
@Deprecated
public void BLOBsEnableAlso() throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\">Also</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to allow the receipt of just
* BLOB property changes.
*
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables)}
*/
@Deprecated
public void BLOBsEnableOnly() throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\">Only</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to disallow the receipt of a
* particular BLOB property changes.
*
* @param property the BLOB property
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables, INDIProperty)}
*/
@Deprecated
public void BLOBsEnableNever(INDIBLOBProperty property) throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\" name=\"" + property.getName() + "\">Never</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to allow the receipt of a
* particular BLOB property changes along with any other property types.
*
* @param property the BLOB property
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables, INDIProperty)}
*/
@Deprecated
public void BLOBsEnableAlso(INDIBLOBProperty property) throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\" name=\"" + property.getName() + "\">Also</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Sends the appropriate message to the Server to allow the receipt of just a
* particular BLOB property changes.
*
* @param property the BLOB property
* @throws IOException if there is some problem sending the message.
* @deprecated Replaced by {@link #BLOBsEnable(laazotea.indi.Constants.BLOBEnables, INDIProperty)}
*/
@Deprecated
public void BLOBsEnableOnly(INDIBLOBProperty property) throws IOException {
String xml = "<enableBLOB device=\"" + getName() + "\" name=\"" + property.getName() + "\">Only</enableBLOB>";
sendMessageToServer(xml);
}
/**
* Gets the last message received from the Device.
*
* @return the last message received.
*/
public String getLastMessage() {
return message;
}
/**
* Gets the timestamp of the last received message.
*
* @return the timestamp of the last received message.
*/
public Date getTimestamp() {
return timestamp;
}
/**
* Gets the name of the Device.
*
* @return the name of the Device.
*/
public String getName() {
return name;
}
/**
* Processes a XML message received for this Device and stores and notifies
* the listeners if there is some message attribute in them.
*
* @param xml the XML message to be processed.
*/
protected void messageReceived(Element xml) {
if (xml.hasAttribute("message")) {
String time = xml.getAttribute("timestamp").trim();
timestamp = INDIDateFormat.parseTimestamp(time);
message = xml.getAttribute("message").trim();
notifyListenersMessageChanged();
}
}
/**
* Processes a XML <delProperty>. It removes the appropriate Property
* from the list of Properties.
*
* @param xml the XML message to be processed.
*/
protected void deleteProperty(Element xml) {
String propertyName = xml.getAttribute("name").trim();
if (!(propertyName.length() == 0)) {
messageReceived(xml);
INDIProperty p = getProperty(propertyName);
if (p != null) {
removeProperty(p);
}
}
}
/**
* This function waits until a Property with a
* <code>name</code> exists in this device and returns it. The wait is
* dinamic, so it should be called from a different Thread or the app will
* freeze until the property exists.
*
* @param name The name of the Property to wait for.
* @return The Property once it exists in this device.
*/
public INDIProperty waitForProperty(String name) {
return waitForProperty(name, Integer.MAX_VALUE);
}
/**
* This function waits until a Property with a
* <code>name</code> exists in this device and returns it. The wait is
* dinamic, so it should be called from a different Thread or the app will
* freeze until the property exists or the
* <code>maxWait</code> number of seconds have elapsed.
*
* @param name The name of the Property to wait for.
* @param maxWait Maximum number of seconds to wait for the Property
* @return The Property once it exists in this Device or
* <code>null</code> if the maximum wait is achieved.
*/
public INDIProperty waitForProperty(String name, int maxWait) {
INDIProperty p = null;
long startTime = (new Date()).getTime();
boolean timeElapsed = false;
while ((p == null) && (!timeElapsed)) {
p = this.getProperty(name);
if (p == null) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
long endTime = (new Date()).getTime();
if (((endTime - startTime) / 1000) > maxWait) {
timeElapsed = true;
}
}
return p;
}
/**
* Processes a XML <setXXXVector>. It updates the appropiate Property.
*
* @param xml the XML message to be processed.
*/
protected void updateProperty(Element xml) {
String propertyName = xml.getAttribute("name").trim();
if (!(propertyName.length() == 0)) {
// check message
messageReceived(xml);
INDIProperty p = getProperty(propertyName);
if (p != null) { // If it does not exist else ignore
if ((p.getClass() == INDITextProperty.class) && (xml.getTagName().compareTo("setTextVector") == 0)) { // If types coincide
p.update(xml);
}
if ((p.getClass() == INDINumberProperty.class) && (xml.getTagName().compareTo("setNumberVector") == 0)) { // If types coincide
p.update(xml);
}
if ((p.getClass() == INDISwitchProperty.class) && (xml.getTagName().compareTo("setSwitchVector") == 0)) { // If types coincide
p.update(xml);
}
if ((p.getClass() == INDILightProperty.class) && (xml.getTagName().compareTo("setLightVector") == 0)) { // If types coincide
p.update(xml);
}
if ((p.getClass() == INDIBLOBProperty.class) && (xml.getTagName().compareTo("setBLOBVector") == 0)) { // If types coincide
p.update(xml);
}
}
}
}
/**
* Processes a XML <newXXXVector>. It creates and adds the appropiate
* Property.
*
* @param xml The XML message to be processed.
*/
protected void addProperty(Element xml) {
String propertyName = xml.getAttribute("name").trim();
if (!(propertyName.length() == 0)) {
messageReceived(xml);
INDIProperty p = getProperty(propertyName);
if (p == null) { // If it does not exist
try {
if (xml.getTagName().compareTo("defSwitchVector") == 0) {
INDISwitchProperty sp = new INDISwitchProperty(xml, this);
addProperty(sp);
notifyListenersNewProperty(sp);
} else if (xml.getTagName().compareTo("defTextVector") == 0) {
INDITextProperty tp = new INDITextProperty(xml, this);
addProperty(tp);
notifyListenersNewProperty(tp);
} else if (xml.getTagName().compareTo("defNumberVector") == 0) {
INDINumberProperty np = new INDINumberProperty(xml, this);
addProperty(np);
notifyListenersNewProperty(np);
} else if (xml.getTagName().compareTo("defLightVector") == 0) {
INDILightProperty lp = new INDILightProperty(xml, this);
addProperty(lp);
notifyListenersNewProperty(lp);
} else if (xml.getTagName().compareTo("defBLOBVector") == 0) {
INDIBLOBProperty bp = new INDIBLOBProperty(xml, this);
addProperty(bp);
notifyListenersNewProperty(bp);
}
} catch (IllegalArgumentException e) { // Some problem with the parameters
e.printStackTrace();
}
}
}
}
/**
* Gets the number of BLOB properties in this Device.
*
* @return the number of BLOB properties in this Device.
*/
public int getBLOBCount() {
return blobCount;
}
/**
* Adds a Property to the properties list and updates the BLOB count if
* necessary.
*
* @param property
*/
private void addProperty(INDIProperty property) {
properties.put(property.getName(), property);
if (property instanceof INDIBLOBProperty) {
blobCount++;
}
}
/**
* Removes a Property from the properties list and updates the BLOB count if
* necessary.
*
* @param property
*/
private void removeProperty(INDIProperty property) {
properties.remove(property.getName());
if (property instanceof INDIBLOBProperty) {
blobCount--;
}
notifyListenersDeleteProperty(property);
}
/**
* Gets the Server Connection of this Device.
*
* @return the Server Connection of this Device.
*/
public INDIServerConnection getServer() {
return server;
}
/**
* Gets a Property by its name.
*
* @param name the name of the Property to be retrieved.
* @return the Property with the
* <code>name</code> or
* <code>null</code> if there is no Property with that name.
*/
public INDIProperty getProperty(String name) {
return properties.get(name);
}
/**
* Gets a list of group names for all the properties.
*
* @return the list of group names for all the properties of the device.
*/
public ArrayList<String> getGroupNames() {
ArrayList<String> groupNames = new ArrayList<String>();
Collection c = properties.values();
Iterator itr = c.iterator();
while (itr.hasNext()) {
INDIProperty p = (INDIProperty) itr.next();
String groupName = p.getGroup();
if (!groupNames.contains(groupName)) {
groupNames.add(groupName);
}
}
return groupNames;
}
/**
* Gets a list of all the properties of the device.
*
* @return the list of Properties belonging to the device
*/
public ArrayList<INDIProperty> getAllProperties() {
ArrayList<INDIProperty> props = new ArrayList<INDIProperty>();
Collection c = properties.values();
Iterator itr = c.iterator();
while (itr.hasNext()) {
INDIProperty p = (INDIProperty) itr.next();
props.add(p);
}
return props;
}
/**
* Gets a list of properties belonging to a group.
*
* @param groupName the name of the group
* @return the list of Properties belonging to the group
*/
public ArrayList<INDIProperty> getPropertiesOfGroup(String groupName) {
ArrayList<INDIProperty> props = new ArrayList<INDIProperty>();
Collection c = properties.values();
Iterator itr = c.iterator();
while (itr.hasNext()) {
INDIProperty p = (INDIProperty) itr.next();
if (p.getGroup().compareTo(groupName) == 0) {
props.add(p);
}
}
return props;
}
/**
* A convenience method to get the Element of a Property by specifiying their
* names.
*
* @param propertyName the name of the Property.
* @param elementName the name of the Element.
* @return the Element with
* <code>elementName</code> as name of the property with
* <code>propertyName</code> as name.
*/
public INDIElement getElement(String propertyName, String elementName) {
INDIProperty p = getProperty(propertyName);
if (p == null) {
return null;
}
return p.getElement(elementName);
}
/**
* Sends a XML message to the Server.
*
* @param XMLMessage the message to be sent.
* @throws IOException if there is some problem with the connection to the
* server.
*/
protected void sendMessageToServer(String XMLMessage) throws IOException {
server.sendMessageToServer(XMLMessage);
}
/**
* Gets a default UI component to handle the repesentation and control of this
* Device. The panel is registered as a listener of this Device. Please note
* that the UI class must implement INDIDeviceListener. 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 component has been asked, it will be dregistered as a
* listener. So, only one default component will listen to the device.
*
* @return A UI component that handles this Device.
* @throws INDIException if no UIComponent is found in the classpath.
*/
public INDIDeviceListener getDefaultUIComponent() throws INDIException {
if (UIComponent != null) {
removeINDIDeviceListener(UIComponent);
}
Object[] arguments = new Object[]{this};
String[] possibleUIClassNames = new String[]{"laazotea.indi.client.ui.INDIDevicePanel", "laazotea.indi.androidui.INDIDeviceView"};
try {
UIComponent = (INDIDeviceListener) ClassInstantiator.instantiate(possibleUIClassNames, arguments);
} catch (ClassCastException e) {
throw new INDIException("The UI component is not a valid INDIDeviceListener. Probably a incorrect library in the classpath.");
}
addINDIDeviceListener(UIComponent);
return UIComponent;
}
/**
* Gets a
* <code>List</code> with all the Properties of this Device.
*
* @return the
* <code>List</code> of Properties belonging to this Device.
*/
public List<INDIProperty> getPropertiesAsList() {
return new ArrayList<INDIProperty>(properties.values());
}
/**
* Gets the names of the Properties of this Device.
*
* @return the names of the Properties of this Device.
*/
public String[] getPropertyNames() {
List<INDIProperty> l = getPropertiesAsList();
String[] names = new String[l.size()];
for (int i = 0; i < l.size(); i++) {
names[i] = l.get(i).getName();
}
return names;
}
}