2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.insteon.internal.message;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.HashMap;
18 import java.util.LinkedHashMap;
20 import java.util.Map.Entry;
22 import javax.xml.parsers.DocumentBuilder;
23 import javax.xml.parsers.DocumentBuilderFactory;
24 import javax.xml.parsers.ParserConfigurationException;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.openhab.binding.insteon.internal.utils.Pair;
28 import org.openhab.binding.insteon.internal.utils.Utils.DataTypeParser;
29 import org.openhab.binding.insteon.internal.utils.Utils.ParsingException;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.Node;
33 import org.w3c.dom.NodeList;
34 import org.xml.sax.SAXException;
37 * Reads the Msg definitions from an XML file
39 * @author Daniel Pfrommer - Initial contribution
40 * @author Rob Nielsen - Port to openHAB 2 insteon binding
43 public class XMLMessageReader {
45 * Reads the message definitions from an xml file
47 * @param input input stream from which to read
48 * @return what was read from file: the map between clear text string and Msg objects
49 * @throws IOException couldn't read file etc
50 * @throws ParsingException something wrong with the file format
51 * @throws FieldException something wrong with the field definition
53 public static Map<String, Msg> readMessageDefinitions(InputStream input)
54 throws IOException, ParsingException, FieldException {
55 Map<String, Msg> messageMap = new HashMap<>();
57 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
58 DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
60 Document doc = dBuilder.parse(input);
61 doc.getDocumentElement().normalize();
63 Node root = doc.getDocumentElement();
65 NodeList nodes = root.getChildNodes();
67 for (int i = 0; i < nodes.getLength(); i++) {
68 Node node = nodes.item(i);
69 if (node.getNodeType() == Node.ELEMENT_NODE) {
70 if (node.getNodeName().equals("msg")) {
71 Pair<String, Msg> msgDef = readMessageDefinition((Element) node);
72 messageMap.put(msgDef.getKey(), msgDef.getValue());
76 } catch (SAXException e) {
77 throw new ParsingException("Failed to parse XML!", e);
78 } catch (ParserConfigurationException e) {
79 throw new ParsingException("Got parser config exception! ", e);
84 private static Pair<String, Msg> readMessageDefinition(Element msg) throws FieldException, ParsingException {
87 LinkedHashMap<Field, Object> fieldMap = new LinkedHashMap<>();
88 String dir = msg.getAttribute("direction");
89 String name = msg.getAttribute("name");
90 Msg.Direction direction = Msg.Direction.getDirectionFromString(dir);
92 if (msg.hasAttribute("length")) {
93 length = Integer.parseInt(msg.getAttribute("length"));
96 NodeList nodes = msg.getChildNodes();
100 for (int i = 0; i < nodes.getLength(); i++) {
101 Node node = nodes.item(i);
102 if (node.getNodeType() == Node.ELEMENT_NODE) {
103 if (node.getNodeName().equals("header")) {
104 int o = readHeaderElement((Element) node, fieldMap);
106 // Increment the offset by the header length
109 Pair<Field, Object> field = readField((Element) node, offset);
110 fieldMap.put(field.getKey(), field.getValue());
111 // Increment the offset
112 offset += field.getKey().getType().getSize();
116 if (offset != length) {
117 throw new ParsingException(
118 "Actual msg length " + offset + " differs from given msg length " + length + "!");
124 return new Pair<>(name, createMsg(fieldMap, length, hlength, direction));
127 private static int readHeaderElement(Element header, LinkedHashMap<Field, Object> fields) throws ParsingException {
129 int headerLen = Integer.parseInt(header.getAttribute("length"));
131 NodeList nodes = header.getChildNodes();
132 for (int i = 0; i < nodes.getLength(); i++) {
133 Node node = nodes.item(i);
134 if (node.getNodeType() == Node.ELEMENT_NODE) {
135 Pair<Field, Object> definition = readField((Element) node, offset);
136 offset += definition.getKey().getType().getSize();
137 fields.put(definition.getKey(), definition.getValue());
140 if (headerLen != offset) {
141 throw new ParsingException(
142 "Actual header length " + offset + " differs from given length " + headerLen + "!");
147 private static Pair<Field, Object> readField(Element field, int offset) {
148 DataType dType = DataType.getDataType(field.getTagName());
149 // Will return blank if no name attribute
150 String name = field.getAttribute("name");
151 Field f = new Field(name, dType, offset);
152 // Now we have field, only need value
153 String sVal = field.getTextContent();
154 Object val = DataTypeParser.parseDataType(dType, sVal);
155 Pair<Field, Object> pair = new Pair<>(f, val);
159 private static Msg createMsg(HashMap<Field, Object> values, int length, int headerLength, Msg.Direction dir)
160 throws FieldException {
161 Msg msg = new Msg(headerLength, new byte[length], length, dir);
162 for (Entry<Field, Object> e : values.entrySet()) {
163 Field f = e.getKey();
164 byte[] data = msg.getData();
166 f.set(data, e.getValue());
168 throw new FieldException("data is null");
170 if (!f.getName().equals("")) {