gt:     protected static List<Field> getAllFields(Class<?> clazz) {
context: /**
   * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
   *
   * See the NOTICE file(s) distributed with this work for additional
   * information regarding copyright ownership.
   *
   * This program and the accompanying materials are made available under the
   * terms of the Eclipse Public License 2.0 which is available at
   * http://www.eclipse.org/legal/epl-2.0
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  package org.eclipse.smarthome.binding.mqtt.generic.internal.mapping;
  
  import java.lang.ref.WeakReference;
  import java.lang.reflect.Field;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.stream.Collectors;
  
  import org.eclipse.jdt.annotation.NonNullByDefault;
  import org.eclipse.jdt.annotation.Nullable;
  import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * <p>
   * MQTT does not directly support key-value configuration maps. We do need those for device discovery and thing
   * configuration though.<br>
   * Different conventions came up with different solutions, and one is to have "attribute classes".
   * </p>
   *
   * <p>
   * An attribute class is a java class that extends {@link AbstractMqttAttributeClass} and
   * contains annotated fields where each field corresponds to a MQTT topic.
   * To automatically subscribe to all fields, a call to
   * {@link #subscribeAndReceive(MqttBrokerConnection, ScheduledExecutorService, String, AttributeChanged, int)} is
   * required.
   * Unsubscribe with a call to {@link #unsubscribe(AbstractMqttAttributeClass)}.
   * </p>
   *
   * <p>
   * The Homie 3.x convention uses attribute classes for Devices, Nodes and Properties configuration.
   * </p>
   *
   * <p>
   * The given object, called bean in this context, can consist of all basic java types boolean, int, double, long,
   * String, respective object wrappers like Integer, Double, Long, the BigDecimal type and Enum types. Enums need to be
   * declared within the bean class though. Arrays like String[] are supported as well, but require an annotation because
   * the separator needs to be known.
   * </p>
   * A topic prefix can be defined for the entire class or for a single field. A field annotation overwrites a class
   * annotation.
   *
   * An example:
   *
   * <pre>
   * &#64;TopicPrefix("$")
   * class MyAttributes extends AbstractMqttAttributeClass {
   *    public String testString;
   *    public @MapToField(splitCharacter=",") String[] multipleStrings;
   *
   *    public int anInt = 2;
   *
   *    public enum AnEnum {
   *      Value1,
   *      Value2
   *    };
   *    public AnEnum anEnum = AnEnum.Value1;
   *
   *    public BigDecimal aDecimalValue
   *
   *    &#64;Override
   *    public Object getFieldsOf() {
   *        return this;
   *    }
   * };
   * </pre>
   *
   * You would use this class in this way:
   *
   * <pre>
   * MyAttributes bean = new MyAttributes();
   * bean.subscribe(connection, new ScheduledExecutorService(), "mqtt/topic/bean", null, 500)
   *         .thenRun(() -> System.out.println("subscribed"));
   * </pre>
   *
   * The above attribute class would end up with subscriptions to "mqtt/topic/bean/$testString",
   * "mqtt/topic/bean/$multipleStrings", "mqtt/topic/bean/$anInt" and so on. It is assumed that all MQTT messages are
   * UTF-8 strings.
   *
   * @author David Graeff - Initial contribution
   */
  @NonNullByDefault
  public abstract class AbstractMqttAttributeClass implements SubscribeFieldToMQTTtopic.FieldChanged {
      private final Logger logger = LoggerFactory.getLogger(AbstractMqttAttributeClass.class);
      protected transient List<SubscribeFieldToMQTTtopic> subscriptions = new ArrayList<>();
      public transient WeakReference<@Nullable MqttBrokerConnection> connection = new WeakReference<>(null);
      protected transient WeakReference<@Nullable ScheduledExecutorService> scheduler = new WeakReference<>(null);
      private final String prefix;
      private transient String basetopic = "";
      protected transient AttributeChanged attributeChangedListener = (b, c, d, e, f) -> {
      };
      private transient boolean complete = false;
  
      /**
       * Implement this interface to be notified of an updated field.
       */
      public interface AttributeChanged {
          /**
           * An attribute has changed
           * 
           * @param name The name of the field
           * @param value The new value
           * @param connection The broker connection
           * @param scheduler The scheduler that was used for timeouts
           * @param allMandatoryFieldsReceived True if now all mandatory fields have values
           */
          void attributeChanged(String name, Object value, MqttBrokerConnection connection,
                  ScheduledExecutorService scheduler, boolean allMandatoryFieldsReceived);
      }
  
      @SuppressWarnings("null")
      protected AbstractMqttAttributeClass() {
          TopicPrefix topicUsesPrefix = getFieldsOf().getClass().getAnnotation(TopicPrefix.class);
          prefix = (topicUsesPrefix != null) ? topicUsesPrefix.value() : "";
      }
  
      /**
       * Unsubscribe from all topics of the managed object.
       *
       * @param connection A broker connection to remove the subscriptions from.
       * @param objectWithFields A bean class
       * @return Returns a future that completes as soon as all unsubscriptions have been performed.
       */
      public CompletableFuture<@Nullable Void> unsubscribe() {
          final MqttBrokerConnection connection = this.connection.get();
          if (connection == null) {
              subscriptions.clear();
              return CompletableFuture.completedFuture(null);
          }
  
          final CompletableFuture<?>[] futures = subscriptions.stream().map(m -> connection.unsubscribe(m.topic, m))
                  .toArray(CompletableFuture[]::new);
          subscriptions.clear();
          return CompletableFuture.allOf(futures);
      }
  
      /**
       * Subscribe to all subtopics on a MQTT broker connection base topic that match field names of s java object.
       * The fields will be kept in sync with their respective topics. Optionally, you can register update-observers for
       * specific fields.
       *
       * @param connection A MQTT broker connection.
       * @param scheduler A scheduler for timeouts.
       * @param basetopic The base topic. Given a base topic of "base/topic", a field "test" would be registered as
       *            "base/topic/test".
       * @param attributeChangedListener Field change listener
       * @param timeout Timeout per subscription in milliseconds. The returned future completes after this time
       *            even if no
       *            message has been received for a single MQTT topic.
       * @return Returns a future that completes as soon as values for all subscriptions have been received or have timed
       *         out.
       */
      public CompletableFuture<@Nullable Void> subscribeAndReceive(MqttBrokerConnection connection,
              ScheduledExecutorService scheduler, String basetopic, @Nullable AttributeChanged attributeChangedListener,
              int timeout) {
  
          // We first need to unsubscribe old subscriptions if any
          final CompletableFuture<@Nullable Void> startFuture;
          if (subscriptions.size() > 0) {
              startFuture = unsubscribe();
          } else {
              startFuture = CompletableFuture.completedFuture(null);
          }
  
          this.connection = new WeakReference<>(connection);
          this.scheduler = new WeakReference<>(scheduler);
          this.basetopic = basetopic;
          if (attributeChangedListener != null) {
              this.attributeChangedListener = attributeChangedListener;
          } else {
              this.attributeChangedListener = (b, c, d, e, f) -> {
              };
          }
  
          subscriptions = getAllFields(getFieldsOf().getClass()).stream().filter(AbstractMqttAttributeClass::filterField)
                  .map(this::mapFieldToSubscriber).collect(Collectors.toList());
  
          final CompletableFuture<?>[] futures = subscriptions.stream()
                  .map(m -> m.subscribeAndReceive(connection, timeout)).toArray(CompletableFuture[]::new);
          return CompletableFuture.allOf(startFuture, CompletableFuture.allOf(futures));
      }
  
      /**
       * Return fields of the given class as well as all super classes.
       *
       * @param clazz The class
       * @return A list of Field objects
       */
  