/**
   * Get a topic object which can be used to publish messages.
   *
   * <p>There are two alternative methods that should be used in preference to this one when
   * publishing a message:
   *
   * <ul>
   *   <li>{@link MqttAsyncClient#publish(String, MqttMessage, MqttDeliveryToken)} to publish a
   *       message in a non-blocking manner or
   *   <li>{@link MqttClient#publishBlock(String, MqttMessage, MqttDeliveryToken)} to publish a
   *       message in a blocking manner
   * </ul>
   *
   * <p>When you build an application, the design of the topic tree should take into account the
   * following principles of topic name syntax and semantics:
   *
   * <ul>
   *   <li>A topic must be at least one character long.
   *   <li>Topic names are case sensitive. For example, <em>ACCOUNTS</em> and <em>Accounts</em> are
   *       two different topics.
   *   <li>Topic names can include the space character. For example, <em>Accounts payable</em> is a
   *       valid topic.
   *   <li>A leading "/" creates a distinct topic. For example, <em>/finance</em> is different from
   *       <em>finance</em>. <em>/finance</em> matches "+/+" and "/+", but not "+".
   *   <li>Do not include the null character (Unicode<samp class="codeph"> \x0000</samp>) in any
   *       topic.
   * </ul>
   *
   * <p>The following principles apply to the construction and content of a topic tree:
   *
   * <ul>
   *   <li>The length is limited to 64k but within that there are no limits to the number of levels
   *       in a topic tree.
   *   <li>There can be any number of root nodes; that is, there can be any number of topic trees.
   * </ul>
   *
   * @param topic the topic to use, for example "finance/stock/ibm".
   * @return an MqttTopic object, which can be used to publish messages to the topic.
   * @throws IllegalArgumentException if the topic contains a '+' or '#' wildcard character.
   */
  protected MqttTopic getTopic(String topic) {
    MqttTopic.validate(topic, false /*wildcards NOT allowed*/);

    MqttTopic result = (MqttTopic) topics.get(topic);
    if (result == null) {
      result = new MqttTopic(topic, comms);
      topics.put(topic, result);
    }
    return result;
  }
  /* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken subscribe(
      String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback)
      throws MqttException {
    final String methodName = "subscribe";

    if (topicFilters.length != qos.length) {
      throw new IllegalArgumentException();
    }

    // remove any message handlers for individual topics
    for (int i = 0; i < topicFilters.length; ++i) {
      this.comms.removeMessageListener(topicFilters[i]);
    }

    String subs = "";
    for (int i = 0; i < topicFilters.length; i++) {
      if (i > 0) {
        subs += ", ";
      }
      subs += "topic=" + topicFilters[i] + " qos=" + qos[i];

      // Check if the topic filter is valid before subscribing
      MqttTopic.validate(topicFilters[i], true /*allow wildcards*/);
    }
    // @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2}
    log.fine(CLASS_NAME, methodName, "106", new Object[] {subs, userContext, callback});

    MqttToken token = new MqttToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);
    token.internalTok.setTopics(topicFilters);

    MqttSubscribe register = new MqttSubscribe(topicFilters, qos);

    comms.sendNoWait(register, token);
    // @TRACE 109=<
    log.fine(CLASS_NAME, methodName, "109");

    return token;
  }
  /* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken unsubscribe(
      String[] topicFilters, Object userContext, IMqttActionListener callback)
      throws MqttException {
    final String methodName = "unsubscribe";
    String subs = "";
    for (int i = 0; i < topicFilters.length; i++) {
      if (i > 0) {
        subs += ", ";
      }
      subs += topicFilters[i];

      // Check if the topic filter is valid before unsubscribing
      // Although we already checked when subscribing, but invalid
      // topic filter is meanless for unsubscribing, just prohibit it
      // to reduce unnecessary control packet send to broker.
      MqttTopic.validate(topicFilters[i], true /*allow wildcards*/);
    }

    // @TRACE 107=Unsubscribe topic={0} userContext={1} callback={2}
    log.fine(CLASS_NAME, methodName, "107", new Object[] {subs, userContext, callback});

    // remove message handlers from the list for this client
    for (int i = 0; i < topicFilters.length; ++i) {
      this.comms.removeMessageListener(topicFilters[i]);
    }

    MqttToken token = new MqttToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);
    token.internalTok.setTopics(topicFilters);

    MqttUnsubscribe unregister = new MqttUnsubscribe(topicFilters);

    comms.sendNoWait(unregister, token);
    // @TRACE 110=<
    log.fine(CLASS_NAME, methodName, "110");

    return token;
  }
  /* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttDeliveryToken publish(
      String topic, MqttMessage message, Object userContext, IMqttActionListener callback)
      throws MqttException, MqttPersistenceException {
    final String methodName = "publish";
    // @TRACE 111=< topic={0} message={1}userContext={1} callback={2}
    log.fine(CLASS_NAME, methodName, "111", new Object[] {topic, userContext, callback});

    // Checks if a topic is valid when publishing a message.
    MqttTopic.validate(topic, false /*wildcards NOT allowed*/);

    MqttDeliveryToken token = new MqttDeliveryToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);
    token.setMessage(message);
    token.internalTok.setTopics(new String[] {topic});

    MqttPublish pubMsg = new MqttPublish(topic, message);
    comms.sendNoWait(pubMsg, token);

    // @TRACE 112=<
    log.fine(CLASS_NAME, methodName, "112");

    return token;
  }