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.device;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.HashMap;
21 import java.util.Map.Entry;
23 import javax.xml.parsers.DocumentBuilder;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.insteon.internal.device.DeviceType.FeatureGroup;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32 import org.w3c.dom.Document;
33 import org.w3c.dom.Element;
34 import org.w3c.dom.Node;
35 import org.w3c.dom.NodeList;
36 import org.xml.sax.SAXException;
39 * Reads the device types from an xml file.
41 * @author Daniel Pfrommer - Initial contribution
42 * @author Bernd Pfrommer - openHAB 1 insteonplm binding
43 * @author Rob Nielsen - Port to openHAB 2 insteon binding
46 public class DeviceTypeLoader {
47 private static final Logger logger = LoggerFactory.getLogger(DeviceTypeLoader.class);
48 private Map<String, DeviceType> deviceTypes = new HashMap<>();
49 private static DeviceTypeLoader deviceTypeLoader = new DeviceTypeLoader();
51 private DeviceTypeLoader() {
52 } // private so nobody can call it
55 * Finds the device type for a given product key
57 * @param aProdKey product key to search for
58 * @return the device type, or null if not found
60 public @Nullable DeviceType getDeviceType(String aProdKey) {
61 return (deviceTypes.get(aProdKey));
65 * Must call loadDeviceTypesXML() before calling this function!
67 * @return currently known device types
69 public Map<String, DeviceType> getDeviceTypes() {
74 * Reads the device types from input stream and stores them in memory for
77 * @param in the input stream from which to read
79 public void loadDeviceTypesXML(InputStream in) throws ParserConfigurationException, SAXException, IOException {
80 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
81 DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
82 Document doc = dBuilder.parse(in);
83 doc.getDocumentElement().normalize();
84 Node root = doc.getDocumentElement();
85 NodeList nodes = root.getChildNodes();
86 for (int i = 0; i < nodes.getLength(); i++) {
87 Node node = nodes.item(i);
88 if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("device")) {
89 processDevice((Element) node);
95 * Reads the device types from file and stores them in memory for later access.
97 * @param aFileName The name of the file to read from
98 * @throws ParserConfigurationException
99 * @throws SAXException
100 * @throws IOException
102 public void loadDeviceTypesXML(String aFileName) throws ParserConfigurationException, SAXException, IOException {
103 File file = new File(aFileName);
104 InputStream in = new FileInputStream(file);
105 loadDeviceTypesXML(in);
109 * Process device node
111 * @param e name of the element to process
112 * @throws SAXException
114 private void processDevice(Element e) throws SAXException {
115 String productKey = e.getAttribute("productKey");
116 if (productKey.equals("")) {
117 throw new SAXException("device in device_types file has no product key!");
119 if (deviceTypes.containsKey(productKey)) {
120 logger.warn("overwriting previous definition of device {}", productKey);
121 deviceTypes.remove(productKey);
123 DeviceType devType = new DeviceType(productKey);
125 NodeList nodes = e.getChildNodes();
126 for (int i = 0; i < nodes.getLength(); i++) {
127 Node node = nodes.item(i);
128 if (node.getNodeType() != Node.ELEMENT_NODE) {
131 Element subElement = (Element) node;
132 if (subElement.getNodeName().equals("model")) {
133 devType.setModel(subElement.getTextContent());
134 } else if (subElement.getNodeName().equals("description")) {
135 devType.setDescription(subElement.getTextContent());
136 } else if (subElement.getNodeName().equals("feature")) {
137 processFeature(devType, subElement);
138 } else if (subElement.getNodeName().equals("feature_group")) {
139 processFeatureGroup(devType, subElement);
141 deviceTypes.put(productKey, devType);
145 private String processFeature(DeviceType devType, Element e) throws SAXException {
146 String name = e.getAttribute("name");
147 if (name.equals("")) {
148 throw new SAXException("feature " + e.getNodeName() + " has feature without name!");
150 if (!name.equals(name.toLowerCase())) {
151 throw new SAXException("feature name '" + name + "' must be lower case");
153 if (!devType.addFeature(name, e.getTextContent())) {
154 throw new SAXException("duplicate feature: " + name);
159 private String processFeatureGroup(DeviceType devType, Element e) throws SAXException {
160 String name = e.getAttribute("name");
161 if (name.equals("")) {
162 throw new SAXException("feature group " + e.getNodeName() + " has no name attr!");
164 String type = e.getAttribute("type");
165 if (type.equals("")) {
166 throw new SAXException("feature group " + e.getNodeName() + " has no type attr!");
168 FeatureGroup fg = new FeatureGroup(name, type);
169 NodeList nodes = e.getChildNodes();
170 for (int i = 0; i < nodes.getLength(); i++) {
171 Node node = nodes.item(i);
172 if (node.getNodeType() != Node.ELEMENT_NODE) {
175 Element subElement = (Element) node;
176 if (subElement.getNodeName().equals("feature")) {
177 fg.addFeature(processFeature(devType, subElement));
178 } else if (subElement.getNodeName().equals("feature_group")) {
179 fg.addFeature(processFeatureGroup(devType, subElement));
182 if (!devType.addFeatureGroup(name, fg)) {
183 throw new SAXException("duplicate feature group " + name);
189 * Helper function for debugging
191 private void logDeviceTypes() {
192 for (Entry<String, DeviceType> dt : getDeviceTypes().entrySet()) {
193 String msg = String.format("%-10s->", dt.getKey()) + dt.getValue();
194 logger.debug("{}", msg);
199 * Singleton instance function, creates DeviceTypeLoader
201 * @return DeviceTypeLoader singleton reference
204 public static synchronized DeviceTypeLoader instance() {
205 if (deviceTypeLoader.getDeviceTypes().isEmpty()) {
206 InputStream input = DeviceTypeLoader.class.getResourceAsStream("/device_types.xml");
209 deviceTypeLoader.loadDeviceTypesXML(input);
211 logger.warn("Resource stream is null, cannot read xml file.");
213 } catch (ParserConfigurationException e) {
214 logger.warn("parser config error when reading device types xml file: ", e);
215 } catch (SAXException e) {
216 logger.warn("SAX exception when reading device types xml file: ", e);
217 } catch (IOException e) {
218 logger.warn("I/O exception when reading device types xml file: ", e);
221 return deviceTypeLoader;