2 * Copyright (c) 2010-2024 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;
15 import java.util.HashMap;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.insteon.internal.InsteonResourceLoader;
21 import org.openhab.binding.insteon.internal.utils.HexUtils;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25 import org.xml.sax.SAXException;
28 * The {@link ProductDataRegistry} represents the product data registry
30 * @author Jeremy Setton - Initial contribution
33 public class ProductDataRegistry extends InsteonResourceLoader {
34 private static final ProductDataRegistry PRODUCT_DATA_REGISTRY = new ProductDataRegistry();
35 private static final String RESOURCE_NAME = "/device-products.xml";
37 private Map<Integer, ProductData> products = new HashMap<>();
39 private ProductDataRegistry() {
44 * Returns the product data for a given dev/sub category
46 * @param deviceCategory device category to match
47 * @param subCategory device subcategory to match
48 * @return product data matching provided parameters
50 public ProductData getProductData(int deviceCategory, int subCategory) {
51 int productId = getProductId(deviceCategory, subCategory);
52 if (!products.containsKey(productId)) {
53 logger.warn("unknown product for devCat:{} subCat:{} in device products xml file",
54 HexUtils.getHexString(deviceCategory), HexUtils.getHexString(subCategory));
55 // fallback to matching product id using device category only
56 productId = getProductId(deviceCategory, ProductData.SUB_CATEGORY_UNKNOWN);
59 return ProductData.makeInsteonProduct(deviceCategory, subCategory, products.get(productId));
63 * Returns the device type for a given dev/sub category
65 * @param deviceCategory device category to match
66 * @param subCategory device subcategory to match
67 * @return device type matching provided parameters
69 public @Nullable DeviceType getDeviceType(int deviceCategory, int subCategory) {
70 return getProductData(deviceCategory, subCategory).getDeviceType();
74 * Returns product id based on dev/sub category
76 * @param deviceCategory device category to use
77 * @param subCategory device subcategory to use
80 private int getProductId(int deviceCategory, int subCategory) {
81 return deviceCategory << 8 | subCategory;
85 * Returns known products
87 * @return currently known products
89 public Map<Integer, ProductData> getProducts() {
94 * Initializes product data registry
97 protected void initialize() {
100 logger.debug("loaded {} products", products.size());
101 if (logger.isTraceEnabled()) {
102 products.values().stream().map(String::valueOf).forEach(logger::trace);
107 * Parses product data document
109 * @param element element to parse
110 * @throws SAXException
113 protected void parseDocument(Element element) throws SAXException {
114 NodeList nodes = element.getChildNodes();
115 for (int i = 0; i < nodes.getLength(); i++) {
116 Node node = nodes.item(i);
117 if (node.getNodeType() == Node.ELEMENT_NODE) {
118 Element child = (Element) node;
119 String nodeName = child.getNodeName();
120 if ("product".equals(nodeName)) {
128 * Parses product node
130 * @param element element to parse
131 * @throws SAXException
133 private void parseProduct(Element element) throws SAXException {
134 int deviceCategory = getHexAttributeAsInteger(element, "devCat", ProductData.DEVICE_CATEGORY_UNKNOWN);
135 int subCategory = getHexAttributeAsInteger(element, "subCat", ProductData.SUB_CATEGORY_UNKNOWN);
136 int productKey = getHexAttributeAsInteger(element, "productKey", 0);
137 int firstRecord = getHexAttributeAsInteger(element, "firstRecord", 0);
138 if (deviceCategory == ProductData.DEVICE_CATEGORY_UNKNOWN) {
139 throw new SAXException("invalid product with no device category in device products xml file");
141 int productId = getProductId(deviceCategory, subCategory);
142 if (products.containsKey(productId)) {
143 logger.warn("overwriting previous definition of product {}", products.get(productId));
146 ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
147 productData.setProductKey(productKey);
148 productData.setFirstRecordLocation(firstRecord);
150 NodeList nodes = element.getChildNodes();
151 for (int i = 0; i < nodes.getLength(); i++) {
152 Node node = nodes.item(i);
153 if (node.getNodeType() == Node.ELEMENT_NODE) {
154 Element child = (Element) node;
155 String nodeName = child.getNodeName();
156 String textContent = child.getTextContent();
157 if ("description".equals(nodeName)) {
158 productData.setDescription(textContent);
159 } else if ("model".equals(nodeName)) {
160 productData.setModel(textContent);
161 } else if ("vendor".equals(nodeName)) {
162 productData.setVendor(textContent);
163 } else if ("device-type".equals(nodeName)) {
164 parseDeviceType(child, productData);
168 products.put(productId, productData);
172 * Parses product device type element
174 * @param element element to parse
175 * @param productData product data to update
176 * @throws SAXException
178 private void parseDeviceType(Element element, ProductData productData) throws SAXException {
179 String deviceType = element.getTextContent();
180 if (deviceType == null) {
181 return; // undefined device type
183 if (DeviceTypeRegistry.getInstance().getDeviceType(deviceType) == null) {
184 throw new SAXException("invalid device type " + deviceType + " in device products xml file");
186 productData.setDeviceType(deviceType);
190 * Singleton instance function
192 * @return ProductDataRegistry singleton reference
194 public static synchronized ProductDataRegistry getInstance() {
195 if (PRODUCT_DATA_REGISTRY.getProducts().isEmpty()) {
196 PRODUCT_DATA_REGISTRY.initialize();
198 return PRODUCT_DATA_REGISTRY;