2 * Copyright (c) 2010-2020 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.apache.commons.lang.StringUtils;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
22 import com.google.gson.Gson;
23 import com.google.gson.TypeAdapter;
24 import com.google.gson.TypeAdapterFactory;
25 import com.google.gson.reflect.TypeToken;
26 import com.google.gson.stream.JsonReader;
27 import com.google.gson.stream.JsonWriter;
30 * This a Gson type adapter factory.
32 * It will create a type adapter for every class derived from {@link BaseChannelConfiguration} and ensures,
33 * that abbreviated names are replaces with their long versions during the read.
35 * In elements, whose name end in'_topic' '~' replacement is performed.
37 * The adapters also handle {@link BaseChannelConfiguration.Device}
39 * @author Jochen Klein - Initial contribution
42 public class ChannelConfigurationTypeAdapterFactory implements TypeAdapterFactory {
46 public <T> TypeAdapter<T> create(@Nullable Gson gson, @Nullable TypeToken<T> type) {
47 if (gson == null || type == null) {
50 if (BaseChannelConfiguration.class.isAssignableFrom(type.getRawType())) {
51 return createHAConfig(gson, type);
53 if (BaseChannelConfiguration.Device.class.isAssignableFrom(type.getRawType())) {
54 return createHADevice(gson, type);
60 * Handle {@link BaseChannelConfiguration}
66 private <T> TypeAdapter<T> createHAConfig(Gson gson, TypeToken<T> type) {
67 /* The delegate is the 'default' adapter */
68 final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
70 return new TypeAdapter<T>() {
72 public T read(@Nullable JsonReader in) throws IOException {
76 /* read the object using the default adapter, but translate the names in the reader */
77 T result = delegate.read(MappingJsonReader.getConfigMapper(in));
78 /* do the '~' expansion afterwards */
79 expandTidleInTopics(BaseChannelConfiguration.class.cast(result));
84 public void write(@Nullable JsonWriter out, T value) throws IOException {
85 delegate.write(out, value);
90 private <T> TypeAdapter<T> createHADevice(Gson gson, TypeToken<T> type) {
91 /* The delegate is the 'default' adapter */
92 final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
94 return new TypeAdapter<T>() {
96 public T read(@Nullable JsonReader in) throws IOException {
100 /* read the object using the default adapter, but translate the names in the reader */
101 T result = delegate.read(MappingJsonReader.getDeviceMapper(in));
106 public void write(@Nullable JsonWriter out, T value) throws IOException {
107 delegate.write(out, value);
112 private void expandTidleInTopics(BaseChannelConfiguration config) {
113 Class<?> type = config.getClass();
115 String tilde = config.tilde;
117 while (type != Object.class) {
118 Field[] fields = type.getDeclaredFields();
120 for (Field field : fields) {
121 if (String.class.isAssignableFrom(field.getType()) && field.getName().endsWith("_topic")) {
122 field.setAccessible(true);
125 final String oldValue = (String) field.get(config);
127 String newValue = oldValue;
128 if (StringUtils.isNotBlank(oldValue)) {
129 if (oldValue.charAt(0) == '~') {
130 newValue = tilde + oldValue.substring(1);
131 } else if (oldValue.charAt(oldValue.length() - 1) == '~') {
132 newValue = oldValue.substring(0, oldValue.length() - 1) + tilde;
136 field.set(config, newValue);
137 } catch (IllegalArgumentException e) {
138 throw new RuntimeException(e);
139 } catch (IllegalAccessException e) {
140 throw new RuntimeException(e);
145 type = type.getSuperclass();