]> git.basschouten.com Git - openhab-addons.git/blob
a3845e6d2ea2d18d496bd9ce896cd6be89d42cea
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.device;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.Map.Entry;
22
23 import javax.xml.parsers.DocumentBuilder;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
26
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;
37
38 /**
39  * Reads the device types from an xml file.
40  *
41  * @author Daniel Pfrommer - Initial contribution
42  * @author Bernd Pfrommer - openHAB 1 insteonplm binding
43  * @author Rob Nielsen - Port to openHAB 2 insteon binding
44  */
45 @NonNullByDefault
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();
50
51     private DeviceTypeLoader() {
52     } // private so nobody can call it
53
54     /**
55      * Finds the device type for a given product key
56      *
57      * @param aProdKey product key to search for
58      * @return the device type, or null if not found
59      */
60     public @Nullable DeviceType getDeviceType(String aProdKey) {
61         return (deviceTypes.get(aProdKey));
62     }
63
64     /**
65      * Must call loadDeviceTypesXML() before calling this function!
66      *
67      * @return currently known device types
68      */
69     public Map<String, DeviceType> getDeviceTypes() {
70         return (deviceTypes);
71     }
72
73     /**
74      * Reads the device types from input stream and stores them in memory for
75      * later access.
76      *
77      * @param in the input stream from which to read
78      */
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);
90             }
91         }
92     }
93
94     /**
95      * Reads the device types from file and stores them in memory for later access.
96      *
97      * @param aFileName The name of the file to read from
98      * @throws ParserConfigurationException
99      * @throws SAXException
100      * @throws IOException
101      */
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);
106     }
107
108     /**
109      * Process device node
110      *
111      * @param e name of the element to process
112      * @throws SAXException
113      */
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!");
118         }
119         if (deviceTypes.containsKey(productKey)) {
120             logger.warn("overwriting previous definition of device {}", productKey);
121             deviceTypes.remove(productKey);
122         }
123         DeviceType devType = new DeviceType(productKey);
124
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) {
129                 continue;
130             }
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);
140             }
141             deviceTypes.put(productKey, devType);
142         }
143     }
144
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!");
149         }
150         if (!name.equals(name.toLowerCase())) {
151             throw new SAXException("feature name '" + name + "' must be lower case");
152         }
153         if (!devType.addFeature(name, e.getTextContent())) {
154             throw new SAXException("duplicate feature: " + name);
155         }
156         return (name);
157     }
158
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!");
163         }
164         String type = e.getAttribute("type");
165         if (type.equals("")) {
166             throw new SAXException("feature group " + e.getNodeName() + " has no type attr!");
167         }
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) {
173                 continue;
174             }
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));
180             }
181         }
182         if (!devType.addFeatureGroup(name, fg)) {
183             throw new SAXException("duplicate feature group " + name);
184         }
185         return (name);
186     }
187
188     /**
189      * Helper function for debugging
190      */
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);
195         }
196     }
197
198     /**
199      * Singleton instance function, creates DeviceTypeLoader
200      *
201      * @return DeviceTypeLoader singleton reference
202      */
203     @Nullable
204     public static synchronized DeviceTypeLoader instance() {
205         if (deviceTypeLoader.getDeviceTypes().isEmpty()) {
206             InputStream input = DeviceTypeLoader.class.getResourceAsStream("/device_types.xml");
207             try {
208                 if (input != null) {
209                     deviceTypeLoader.loadDeviceTypesXML(input);
210                 } else {
211                     logger.warn("Resource stream is null, cannot read xml file.");
212                 }
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);
219             }
220         }
221         return deviceTypeLoader;
222     }
223 }