]> git.basschouten.com Git - openhab-addons.git/blob
ab4d08efb23fee5b10c398c2472156566c01f336
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.config;
14
15 import java.io.IOException;
16 import java.lang.reflect.Field;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.mqtt.homeassistant.internal.MappingJsonReader;
21 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
22 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
23
24 import com.google.gson.Gson;
25 import com.google.gson.TypeAdapter;
26 import com.google.gson.TypeAdapterFactory;
27 import com.google.gson.reflect.TypeToken;
28 import com.google.gson.stream.JsonReader;
29 import com.google.gson.stream.JsonWriter;
30
31 /**
32  * This a Gson type adapter factory.
33  *
34  * <p>
35  * It will create a type adapter for every class derived from {@link
36  * AbstractChannelConfiguration} and ensures,
37  * that abbreviated names are replaces with their long versions during the read.
38  *
39  * <p>
40  * In elements, whose name end in'_topic' '~' replacement is performed.
41  *
42  * <p>
43  * The adapters also handle {@link Device}
44  *
45  * @author Jochen Klein - Initial contribution
46  */
47 @NonNullByDefault
48 public class ChannelConfigurationTypeAdapterFactory implements TypeAdapterFactory {
49
50     @Override
51     @Nullable
52     public <T> TypeAdapter<T> create(@Nullable Gson gson, @Nullable TypeToken<T> type) {
53         if (gson == null || type == null) {
54             return null;
55         }
56         if (AbstractChannelConfiguration.class.isAssignableFrom(type.getRawType())) {
57             return createHAConfig(gson, type);
58         }
59         if (Device.class.isAssignableFrom(type.getRawType())) {
60             return createHADevice(gson, type);
61         }
62         return null;
63     }
64
65     /**
66      * Handle {@link
67      * AbstractChannelConfiguration}
68      *
69      * @param gson parser
70      * @param type type
71      * @return adapter
72      */
73     private <T> TypeAdapter<T> createHAConfig(Gson gson, TypeToken<T> type) {
74         /* The delegate is the 'default' adapter */
75         final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
76
77         return new TypeAdapter<T>() {
78             @Override
79             public @Nullable T read(JsonReader in) throws IOException {
80                 /* read the object using the default adapter, but translate the names in the reader */
81                 T result = delegate.read(MappingJsonReader.getConfigMapper(in));
82                 /* do the '~' expansion afterwards */
83                 expandTidleInTopics(AbstractChannelConfiguration.class.cast(result));
84                 return result;
85             }
86
87             @Override
88             public void write(JsonWriter out, @Nullable T value) throws IOException {
89                 delegate.write(out, value);
90             }
91         };
92     }
93
94     private <T> TypeAdapter<T> createHADevice(Gson gson, TypeToken<T> type) {
95         /* The delegate is the 'default' adapter */
96         final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
97
98         return new TypeAdapter<T>() {
99             @Override
100             public @Nullable T read(JsonReader in) throws IOException {
101                 /* read the object using the default adapter, but translate the names in the reader */
102                 T result = delegate.read(MappingJsonReader.getDeviceMapper(in));
103                 return result;
104             }
105
106             @Override
107             public void write(JsonWriter out, @Nullable T value) throws IOException {
108                 delegate.write(out, value);
109             }
110         };
111     }
112
113     private void expandTidleInTopics(AbstractChannelConfiguration config) {
114         Class<?> type = config.getClass();
115
116         String tilde = config.getTilde();
117
118         while (type != Object.class) {
119             Field[] fields = type.getDeclaredFields();
120
121             for (Field field : fields) {
122                 if (String.class.isAssignableFrom(field.getType()) && field.getName().endsWith("_topic")) {
123                     field.setAccessible(true);
124
125                     try {
126                         final String oldValue = (String) field.get(config);
127
128                         String newValue = oldValue;
129                         if (oldValue != null && !oldValue.isBlank()) {
130                             if (oldValue.charAt(0) == '~') {
131                                 newValue = tilde + oldValue.substring(1);
132                             } else if (oldValue.charAt(oldValue.length() - 1) == '~') {
133                                 newValue = oldValue.substring(0, oldValue.length() - 1) + tilde;
134                             }
135                         }
136
137                         field.set(config, newValue);
138                     } catch (IllegalArgumentException | IllegalAccessException e) {
139                         throw new RuntimeException(e);
140                     }
141                 }
142             }
143
144             type = type.getSuperclass();
145         }
146     }
147 }