gt:             for (DeviceFeature i : features.values()) {
context: /**
   * Copyright (c) 2010-2020 Contributors to the openHAB project
   *
   * See the NOTICE file(s) distributed with this work for additional
   * information.
   *
   * 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.openhab.binding.insteon.internal.device;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.PriorityQueue;
  
  import org.eclipse.jdt.annotation.NonNullByDefault;
  import org.eclipse.jdt.annotation.Nullable;
  import org.openhab.binding.insteon.internal.config.InsteonChannelConfiguration;
  import org.openhab.binding.insteon.internal.device.DeviceType.FeatureGroup;
  import org.openhab.binding.insteon.internal.device.GroupMessageStateMachine.GroupMessage;
  import org.openhab.binding.insteon.internal.driver.Driver;
  import org.openhab.binding.insteon.internal.message.FieldException;
  import org.openhab.binding.insteon.internal.message.InvalidMessageTypeException;
  import org.openhab.binding.insteon.internal.message.Msg;
  import org.openhab.core.types.Command;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * The InsteonDevice class holds known per-device state of a single Insteon device,
   * including the address, what port(modem) to reach it on etc.
   * Note that some Insteon devices de facto consist of two devices (let's say
   * a relay and a sensor), but operate under the same address. Such devices will
   * be represented just by a single InsteonDevice. Their different personalities
   * will then be represented by DeviceFeatures.
   *
   * @author Bernd Pfrommer - Initial contribution
   * @author Rob Nielsen - Port to openHAB 2 insteon binding
   */
  @NonNullByDefault
  public class InsteonDevice {
      private final Logger logger = LoggerFactory.getLogger(InsteonDevice.class);
  
      public static enum DeviceStatus {
          INITIALIZED,
          POLLING
      }
  
      /** need to wait after query to avoid misinterpretation of duplicate replies */
      private static final int QUIET_TIME_DIRECT_MESSAGE = 2000;
      /** how far to space out poll messages */
      private static final int TIME_BETWEEN_POLL_MESSAGES = 1500;
  
      private InsteonAddress address = new InsteonAddress();
      private long pollInterval = -1L; // in milliseconds
      private @Nullable Driver driver = null;
      private Map<String, DeviceFeature> features = new HashMap<>();
      private @Nullable String productKey = null;
      private volatile long lastTimePolled = 0L;
      private volatile long lastMsgReceived = 0L;
      private boolean isModem = false;
      private PriorityQueue<@Nullable QEntry> mrequestQueue = new PriorityQueue<>();
      private @Nullable DeviceFeature featureQueried = null;
      private long lastQueryTime = 0L;
      private boolean hasModemDBEntry = false;
      private DeviceStatus status = DeviceStatus.INITIALIZED;
      private Map<Integer, GroupMessageStateMachine> groupState = new HashMap<>();
      private Map<String, Object> deviceConfigMap = new HashMap<String, Object>();
  
      /**
       * Constructor
       */
      public InsteonDevice() {
          lastMsgReceived = System.currentTimeMillis();
      }
  
      // --------------------- simple getters -----------------------------
  
      public boolean hasProductKey() {
          return productKey != null;
      }
  
      public @Nullable String getProductKey() {
          return productKey;
      }
  
      public boolean hasModemDBEntry() {
          return hasModemDBEntry;
      }
  
      public DeviceStatus getStatus() {
          return status;
      }
  
      public InsteonAddress getAddress() {
          return (address);
      }
  
      public @Nullable Driver getDriver() {
          return driver;
      }
  
      public long getPollInterval() {
          return pollInterval;
      }
  
      public boolean isModem() {
          return isModem;
      }
  
      public @Nullable DeviceFeature getFeature(String f) {
          return features.get(f);
      }
  
      public Map<String, DeviceFeature> getFeatures() {
          return features;
      }
  
      public byte getX10HouseCode() {
          return (address.getX10HouseCode());
      }
  
      public byte getX10UnitCode() {
          return (address.getX10UnitCode());
      }
  
      public boolean hasProductKey(String key) {
          String productKey = this.productKey;
          return productKey != null && productKey.equals(key);
      }
  
      public boolean hasValidPollingInterval() {
          return (pollInterval > 0);
      }
  
      public long getPollOverDueTime() {
          return (lastTimePolled - lastMsgReceived);
      }
  
      public boolean hasAnyListeners() {
          synchronized (features) {
              for (DeviceFeature f : features.values()) {
                  if (f.hasListeners()) {
                      return true;
                  }
              }
          }
          return false;
      }
      // --------------------- simple setters -----------------------------
  
      public void setStatus(DeviceStatus aI) {
          status = aI;
      }
  
      public void setHasModemDBEntry(boolean b) {
          hasModemDBEntry = b;
      }
  
      public void setAddress(InsteonAddress ia) {
          address = ia;
      }
  
      public void setDriver(Driver d) {
          driver = d;
      }
  
      public void setIsModem(boolean f) {
          isModem = f;
      }
  
      public void setProductKey(String pk) {
          productKey = pk;
      }
  
      public void setPollInterval(long pi) {
          logger.trace("setting poll interval for {} to {} ", address, pi);
          if (pi > 0) {
              pollInterval = pi;
          }
      }
  
      public void setFeatureQueried(@Nullable DeviceFeature f) {
          synchronized (mrequestQueue) {
              featureQueried = f;
          }
      }
  
      public void setDeviceConfigMap(Map<String, Object> deviceConfigMap) {
          this.deviceConfigMap = deviceConfigMap;
      }
  
      public Map<String, Object> getDeviceConfigMap() {
          return deviceConfigMap;
      }
  
      public @Nullable DeviceFeature getFeatureQueried() {
          synchronized (mrequestQueue) {
              return (featureQueried);
          }
      }
  
      /**
       * Removes feature listener from this device
       *
       * @param aItemName name of the feature listener to remove
       * @return true if a feature listener was successfully removed
       */
      public boolean removeFeatureListener(String aItemName) {
          boolean removedListener = false;
          synchronized (features) {
              for (Iterator<Entry<String, DeviceFeature>> it = features.entrySet().iterator(); it.hasNext();) {
                  DeviceFeature f = it.next().getValue();
                  if (f.removeListener(aItemName)) {
                      removedListener = true;
                  }
              }
          }
          return removedListener;
      }
  
      /**
       * Invoked to process an openHAB command
       *
       * @param driver The driver to use
       * @param c The item configuration
       * @param command The actual command to execute
       */
      public void processCommand(Driver driver, InsteonChannelConfiguration c, Command command) {
          logger.debug("processing command {} features: {}", command, features.size());
          synchronized (features) {
              for (DeviceFeature i : features.values()) {
                  if (i.isReferencedByItem(c.getChannelName())) {
                      i.handleCommand(c, command);
                  }
              }
          }
      }
  
      /**
       * Execute poll on this device: create an array of messages,
       * add them to the request queue, and schedule the queue
       * for processing.
       *
       * @param delay scheduling delay (in milliseconds)
       */
      public void doPoll(long delay) {
          long now = System.currentTimeMillis();
          List<QEntry> l = new ArrayList<>();
          synchronized (features) {
              int spacing = 0;
  