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 @Nullable T read(JsonReader in) throws IOException {
73 /* read the object using the default adapter, but translate the names in the reader */
74 T result = delegate.read(MappingJsonReader.getConfigMapper(in));
75 /* do the '~' expansion afterwards */
76 expandTidleInTopics(BaseChannelConfiguration.class.cast(result));
81 public void write(JsonWriter out, @Nullable T value) throws IOException {
82 delegate.write(out, value);
87 private <T> TypeAdapter<T> createHADevice(Gson gson, TypeToken<T> type) {
88 /* The delegate is the 'default' adapter */
89 final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
91 return new TypeAdapter<T>() {
93 public @Nullable T read(JsonReader in) throws IOException {
94 /* read the object using the default adapter, but translate the names in the reader */
95 T result = delegate.read(MappingJsonReader.getDeviceMapper(in));
100 public void write(JsonWriter out, @Nullable T value) throws IOException {
101 delegate.write(out, value);
106 private void expandTidleInTopics(BaseChannelConfiguration config) {
107 Class<?> type = config.getClass();
109 String tilde = config.tilde;
111 while (type != Object.class) {
112 Field[] fields = type.getDeclaredFields();
114 for (Field field : fields) {
115 if (String.class.isAssignableFrom(field.getType()) && field.getName().endsWith("_topic")) {
116 field.setAccessible(true);
119 final String oldValue = (String) field.get(config);
121 String newValue = oldValue;
122 if (StringUtils.isNotBlank(oldValue)) {
123 if (oldValue.charAt(0) == '~') {
124 newValue = tilde + oldValue.substring(1);
125 } else if (oldValue.charAt(oldValue.length() - 1) == '~') {
126 newValue = oldValue.substring(0, oldValue.length() - 1) + tilde;
130 field.set(config, newValue);
131 } catch (IllegalArgumentException e) {
132 throw new RuntimeException(e);
133 } catch (IllegalAccessException e) {
134 throw new RuntimeException(e);
139 type = type.getSuperclass();