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.mqtt.homeassistant.internal;
15 import java.io.IOException;
16 import java.lang.reflect.Field;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
21 import com.google.gson.Gson;
22 import com.google.gson.TypeAdapter;
23 import com.google.gson.TypeAdapterFactory;
24 import com.google.gson.reflect.TypeToken;
25 import com.google.gson.stream.JsonReader;
26 import com.google.gson.stream.JsonWriter;
29 * This a Gson type adapter factory.
31 * It will create a type adapter for every class derived from {@link BaseChannelConfiguration} and ensures,
32 * that abbreviated names are replaces with their long versions during the read.
34 * In elements, whose name end in'_topic' '~' replacement is performed.
36 * The adapters also handle {@link BaseChannelConfiguration.Device}
38 * @author Jochen Klein - Initial contribution
41 public class ChannelConfigurationTypeAdapterFactory implements TypeAdapterFactory {
45 public <T> TypeAdapter<T> create(@Nullable Gson gson, @Nullable TypeToken<T> type) {
46 if (gson == null || type == null) {
49 if (BaseChannelConfiguration.class.isAssignableFrom(type.getRawType())) {
50 return createHAConfig(gson, type);
52 if (BaseChannelConfiguration.Device.class.isAssignableFrom(type.getRawType())) {
53 return createHADevice(gson, type);
59 * Handle {@link BaseChannelConfiguration}
65 private <T> TypeAdapter<T> createHAConfig(Gson gson, TypeToken<T> type) {
66 /* The delegate is the 'default' adapter */
67 final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
69 return new TypeAdapter<T>() {
71 public @Nullable T read(JsonReader in) throws IOException {
72 /* read the object using the default adapter, but translate the names in the reader */
73 T result = delegate.read(MappingJsonReader.getConfigMapper(in));
74 /* do the '~' expansion afterwards */
75 expandTidleInTopics(BaseChannelConfiguration.class.cast(result));
80 public void write(JsonWriter out, @Nullable T value) throws IOException {
81 delegate.write(out, value);
86 private <T> TypeAdapter<T> createHADevice(Gson gson, TypeToken<T> type) {
87 /* The delegate is the 'default' adapter */
88 final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
90 return new TypeAdapter<T>() {
92 public @Nullable T read(JsonReader in) throws IOException {
93 /* read the object using the default adapter, but translate the names in the reader */
94 T result = delegate.read(MappingJsonReader.getDeviceMapper(in));
99 public void write(JsonWriter out, @Nullable T value) throws IOException {
100 delegate.write(out, value);
105 private void expandTidleInTopics(BaseChannelConfiguration config) {
106 Class<?> type = config.getClass();
108 String tilde = config.tilde;
110 while (type != Object.class) {
111 Field[] fields = type.getDeclaredFields();
113 for (Field field : fields) {
114 if (String.class.isAssignableFrom(field.getType()) && field.getName().endsWith("_topic")) {
115 field.setAccessible(true);
118 final String oldValue = (String) field.get(config);
120 String newValue = oldValue;
121 if (oldValue != null && !oldValue.isBlank()) {
122 if (oldValue.charAt(0) == '~') {
123 newValue = tilde + oldValue.substring(1);
124 } else if (oldValue.charAt(oldValue.length() - 1) == '~') {
125 newValue = oldValue.substring(0, oldValue.length() - 1) + tilde;
129 field.set(config, newValue);
130 } catch (IllegalArgumentException e) {
131 throw new RuntimeException(e);
132 } catch (IllegalAccessException e) {
133 throw new RuntimeException(e);
138 type = type.getSuperclass();