/*
* CmdLineHandler.java
* v0.10
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFrame;
import DMCLib.XMLUtils;
import org.w3c.dom.*;
/**
* Tulkki joka toimii käyttäjän ja DMCLiten välissä.
*/
public class CmdLineHandler extends JFrame {
protected int interPort = 12010;
protected String interAddr = "127.0.0.1";
protected final static String TITLE = "CmdLineHandler";
// Tämä on aloituspolku, josta lähdetään liikkeelle
private final String startPath = "/zones/Home/";
// Tähän on tallennettu polku, jossa käyttäjä on tällä hetkellä
private String path = "/zones/Home/";
// Tähän on tallennettu käyttäjän aiempi polku
private String prevPath = "/zones/Home/";
// Viimeisin suoritettu komento
private String latestCommand;
/*
* Oliko kyseessä laitteen asetusten asettamisviesti? (polun palauttamista
* varten).
*/
private boolean setMessage = false;
/**
* Muuttaa käyttäjän komentoriviltä syöttämän komennon DMC:n ymmärtämäksi
* komennoksi.
*
* @param cmdLine
* Käyttäjän antama komento
* @return DMC:lle lähtevä komento
*/
protected String onUserInput(String cmdLine) {
// Ota komento ylös
this.latestCommand = cmdLine;
/*
* Jos polkua ei ole asetettu, aseta se aloituspoluksi ja edelliseksi
* poluksi.
*/
if (this.path == null) {
this.path = this.startPath;
this.prevPath = this.path;
}
// Tämä palautetaan DMC:lle
String toDMC = null;
/*
* Alusta patterneja, joita etsitään käyttäjän antamista komennoista,
* jolloin huomataan huoneen vaihto (cd huone) ja laitteen asetuksen
* asettaminen (set setting=value).
*/
Pattern a = Pattern.compile("cd\\s*(.*)");
Matcher changeDir = a.matcher(cmdLine.trim());
Pattern b = Pattern.compile("set\\s*(\\w*)\\=(\\w*)");
Matcher setValue = b.matcher(cmdLine.trim());
/*
* Jos kyseessä on liikkuminen polussa yhden tason alaspäin.
*/
if (cmdLine.trim().equals("cd..")) {
// Jos polku on jo aloituspolku, ei polkua muuteta
if (!this.path.equals(this.startPath)) {
/*
* Osita nykyinen polku ja poista polun viimeinen osa, jolloin
* siirrytään tasossa yksi alaspäin.
*/
String[] split = this.path.split("/");
String newPath = "";
for (int i = 0; i < split.length - 1; i++) {
newPath += split[i] + "/";
}
// Aseta edellinen polku ja uusi polku
this.prevPath = this.path;
this.path = newPath;
}
// Listaa huoneet/laitteet polussa
toDMC = "get " + this.path;
}
/* Dir- ja ls- komennot listaavat huoneet/laitteet polussa */
else if (cmdLine.trim().equals("dir") || cmdLine.trim().equals("ls")) {
toDMC = "get " + this.path;
}
/* Jos komento oli polussa liikuminen eteenpäin. */
else if (changeDir.matches()) {
// Jos polku on asetettu
if (changeDir.group(1) != null && !changeDir.group(1).isEmpty()) {
String newPath = changeDir.group(1);
this.prevPath = this.path;
/*
* Jos pelkkä kauttaviiva, mennään aloituspolkuun. Jos alkaa
* kauttaviivalla, on kyeessä absoluuttinen osoite. Muutoin
* osoite suhteellinen ja siirrytään polussa eteenpäin.
*/
if (newPath.equals("/"))
this.path = this.startPath;
else if (newPath.startsWith("/"))
this.path = (this.startPath + (newPath + "/")).replaceAll(
"//", "/");
else
this.path += (newPath + "/").replaceAll("//", "/");
}
// Listaa huoneet/laitteet polussa
toDMC = "get " + this.path;
}
/* Kyseessä laitteen asetusten muuttamis-komento */
else if (setValue.matches()) {
if (setValue.group(1) != null && setValue.group(2) != null) {
// Lue arvot
String variable = setValue.group(1);
String value = setValue.group(2);
// Boolean-muuttujat ovat ON ja OFF
if (value.equals("ON"))
value = "1";
else if (value.equals("OFF"))
value = "0";
// Komennon tyyppi ole asetuksen muuttaminen
this.setMessage = true;
// Päivitä arvo
toDMC = ("put " + this.path + "?" + variable + "=" + value)
.replaceAll("/\\?", "\\?");
}
}
/* Helpin tulostus */
else if (cmdLine.trim().equals("help")) {
toDMC = "== DMC help == \n\n" +
"Available commands:\n"+
"cd Change zone e.g 'cd Hall' or 'cd /Bathroom/Lights'\n"+
"cd.. Change zone one level down\n"+
"dir List all zones/devices or device's settings\n"+
"ls Same as 'dir'\n"+
"help This help\n"+
"set = Sets the setting to value in a device (first browse to the device)\n"+
"";
}
/* Kaikki muut komennot palauttavat alert-viestin */
else
toDMC = "Error! Invalid command:\n" + "'" + this.latestCommand
+ "'" + " at path: /"
+ this.path.replaceAll(this.startPath, "") + "";
return toDMC;
}
/**
* Tämän metodin tehtävänä on konvertoida DMC:n palaute tai notifikaatio
* käyttäjäystävällisempään muotoon.
*
* @param xmlMessage
* DMC:ltä tuleva XML-muotoinen viesti
* @return Käyttäjälle tulostuva viesti
*/
protected String onDMCInput(String xmlMessage) {
/*
* =========================================== XML-viestin DOM-puun
* rakennus ja läpikäynti. ===========================================
*/
Document dom = XMLUtils.asDOM(xmlMessage);
String message = "";
Element root = dom.getDocumentElement();
/* Katso, onko kyseessä error-viesti. */
if (root.getNodeName().equals("respCode")) {
/*
* Jos 404 -errori, palauta käyttäjälle virheviesti. Hakemiston
* vaihdon tapauksessa, aseta polku edelliseen polkuun (joka on
* olemassa).
*/
if (root.getTextContent().equals("404")) {
String errorMessage = "Error! Invalid command:\n" + "'"
+ this.latestCommand + "'" + " at path: /"
+ this.prevPath.replaceAll(this.startPath, "") + "\n";
if (this.setMessage) {
this.setMessage = false;
} else {
this.path = this.prevPath;
}
return errorMessage;
}
}
/*
* Laitteen asetuksen tilan katsominen. Jos onnistunut laitteen
* asetuksen asetus, kerro käyttäjälle
* onnistuneesta asetuksesta.
*/
else if (root.getNodeName().equals("var")) {
String name = root.getAttribute("name");
String value = root.getAttribute("value");
if (value.equals("false"))
value = "OFF";
else
value = "ON";
String returnMessage = "Device: " + "/"
+ this.path.replaceAll(this.startPath, "") + "\n";
/* Tulosta eri viestit riippuen siitä asetettiinko
* asetus vai luetaanko vain sitä.
*/
if (this.setMessage) {
returnMessage += "Succesfully set the setting " + "'" + name
+ "'" + " to " + "'" + value + "'" + "\n";
} else {
returnMessage += "The setting " + "'" + name + "'"
+ " is set to " + "'" + value + "'" + "\n";
}
return returnMessage;
}
/*
* Jos kyseessä ei ollut error-viesti tai onnistuneen asetuksen asetus,
* päästään tänne.
*/
NodeList nodes = root.getChildNodes();
int numNodes = nodes.getLength();
// Onko otsikkorivi jo tulostettu viestiin?
boolean titleSet = false;
// Looppaa läpi kaikki nodet puusta
for (int i = 0; i < numNodes; i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
// Ota ylös noden tyyppi (joko zone, device tai var)
String type = e.getNodeName();
/*
* Tulosta tietoa käyttäjälle riippuen siitä, mikä on noden
* tyyppi.
*/
if (type.equals("zone")) {
if (!titleSet) {
message += "Zone: " + "/"
+ this.path.replaceAll(this.startPath, "")
+ "\n";
message += "Available zones (+) and devices (-):" + "\n";
titleSet = true;
}
message += " + " + e.getAttribute("name") + "\n";
} else if (type.equals("device")) {
if (!titleSet) {
message += "Zone: " + "/"
+ this.path.replaceAll(this.startPath, "")
+ "\n";
message += "Available zones (+) and devices (-):" + "\n";
titleSet = true;
}
message += " - " + e.getAttribute("name") + "\n";
} else if (type.equals("var")) {
if (!titleSet) {
message += "Device: " + "/"
+ this.path.replaceAll(this.startPath, "")
+ "\n";
message += "Device settings:" + "\n";
message += " " + "Setting" + "\t" + "Value" + "\n";
titleSet = true;
}
if (e.getAttribute("type").equals("bool")) {
// Tulosta true muodossa ON ja false muodossa OFF
String value = "OFF";
if (e.getAttribute("value").equals("true"))
value = "ON";
message += " " + e.getAttribute("name") + "\t\t" + value
+ "\n";
} else {
message += " " + e.getAttribute("name") + "\t"
+ e.getAttribute("value") + "\n";
}
}
}
}
return message;
}
// -------------------------------------------------------------------------
// ctor
//
public CmdLineHandler() {
super(TITLE);
serve();
setVisible(true);
}
// -------------------------------------------------------------------------
//
protected void serve() {
class InterThread extends Thread {
public void run() {
InetAddress ina = null;
try {
ina = InetAddress.getByName(interAddr);
} catch (IOException ex) {
}
while (true)
try {
// -- wait for request (blocks)
ServerSocket interpreter;
if (ina != null)
interpreter = new ServerSocket(interPort, 5, ina);
else
interpreter = new ServerSocket(interPort);
Socket clientSocket = interpreter.accept();
// -- read request
InputStream is = clientSocket.getInputStream();
StringBuffer sb = new StringBuffer();
int c, count = 0;
while ((c = is.read()) != -1) {
if ((c == 0) || (++count > 16384))
break;
sb.append((char) c);
}
if (count <= 16384) {
// -- process
String resp = null;
String req = sb.toString();
if (req.startsWith("usr:"))
resp = onUserInput(req.substring(4));
else if (req.startsWith("dmc:"))
resp = onDMCInput(req.substring(4));
else
resp = "unknown request";
// -- reply
OutputStream os = clientSocket.getOutputStream();
if (resp != null)
os.write(resp.getBytes());
os.write(0);
os.flush();
os.close();
}
is.close();
clientSocket.close();
interpreter.close();
} catch (IOException ex) {
System.out.println(ex);
}
}
}
InterThread interThread = new InterThread();
interThread.start();
}
// -------------------------------------------------------------------------
// entry point
//
public static void main(String[] args) {
try {
CmdLineHandler mainFrame = new CmdLineHandler();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}