/**
   * Create an MqttAsyncClient that is used to communicate with an MQTT server.
   *
   * <p>The address of a server can be specified on the constructor. Alternatively a list containing
   * one or more servers can be specified using the {@link
   * MqttConnectOptions#setServerURIs(String[]) setServerURIs} method on MqttConnectOptions.
   *
   * <p>The <code>serverURI</code> parameter is typically used with the the <code>clientId</code>
   * parameter to form a key. The key is used to store and reference messages while they are being
   * delivered. Hence the serverURI specified on the constructor must still be specified even if a
   * list of servers is specified on an MqttConnectOptions object. The serverURI on the constructor
   * must remain the same across restarts of the client for delivery of messages to be maintained
   * from a given client to a given server or set of servers.
   *
   * <p>The address of the server to connect to is specified as a URI. Two types of connection are
   * supported <code>tcp://</code> for a TCP connection and <code>ssl://</code> for a TCP connection
   * secured by SSL/TLS. For example:
   *
   * <ul>
   *   <li><code>tcp://localhost:1883</code>
   *   <li><code>ssl://localhost:8883</code>
   * </ul>
   *
   * If the port is not specified, it will default to 1883 for <code>tcp://</code>" URIs, and 8883
   * for <code>ssl://</code> URIs.
   *
   * <p>A client identifier <code>clientId</code> must be specified and be less that 65535
   * characters. It must be unique across all clients connecting to the same server. The clientId is
   * used by the server to store data related to the client, hence it is important that the clientId
   * remain the same when connecting to a server if durable subscriptions or reliable messaging are
   * required.
   *
   * <p>A convenience method is provided to generate a random client id that should satisfy this
   * criteria - {@link #generateClientId()}. As the client identifier is used by the server to
   * identify a client when it reconnects, the client must use the same identifier between
   * connections if durable subscriptions or reliable delivery of messages is required.
   *
   * <p>In Java SE, SSL can be configured in one of several ways, which the client will use in the
   * following order:
   *
   * <ul>
   *   <li><strong>Supplying an <code>SSLSocketFactory</code></strong> - applications can use {@link
   *       MqttConnectOptions#setSocketFactory(SocketFactory)} to supply a factory with the
   *       appropriate SSL settings.
   *   <li><strong>SSL Properties</strong> - applications can supply SSL settings as a simple Java
   *       Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
   *   <li><strong>Use JVM settings</strong> - There are a number of standard Java system properties
   *       that can be used to configure key and trust stores.
   * </ul>
   *
   * <p>In Java ME, the platform settings are used for SSL connections.
   *
   * <p>A persistence mechanism is used to enable reliable messaging. For messages sent at qualities
   * of service (QoS) 1 or 2 to be reliably delivered, messages must be stored (on both the client
   * and server) until the delivery of the message is complete. If messages are not safely stored
   * when being delivered then a failure in the client or server can result in lost messages. A
   * pluggable persistence mechanism is supported via the {@link MqttClientPersistence} interface.
   * An implementer of this interface that safely stores messages must be specified in order for
   * delivery of messages to be reliable. In addition {@link
   * MqttConnectOptions#setCleanSession(boolean)} must be set to false. In the event that only QoS 0
   * messages are sent or received or cleanSession is set to true then a safe store is not needed.
   *
   * <p>An implementation of file-based persistence is provided in class {@link
   * MqttDefaultFilePersistence} which will work in all Java SE based systems. If no persistence is
   * needed, the persistence parameter can be explicitly set to <code>null</code>.
   *
   * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden
   *     using {@link MqttConnectOptions#setServerURIs(String[])}
   * @param clientId a client identifier that is unique on the server being connected to
   * @param persistence the persistence class to use to store in-flight message. If null then the
   *     default persistence mechanism is used
   * @throws IllegalArgumentException if the URI does not start with "tcp://", "ssl://" or
   *     "local://"
   * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in
   *     length
   * @throws MqttException if any other problem was encountered
   */
  public MqttAsyncClient(
      String serverURI,
      String clientId,
      MqttClientPersistence persistence,
      MqttPingSender pingSender)
      throws MqttException {
    final String methodName = "MqttAsyncClient";

    log.setResourceName(clientId);

    if (clientId == null) { // Support empty client Id, 3.1.1 standard
      throw new IllegalArgumentException("Null clientId");
    }
    // Count characters, surrogate pairs count as one character.
    int clientIdLength = 0;
    for (int i = 0; i < clientId.length() - 1; i++) {
      if (Character_isHighSurrogate(clientId.charAt(i))) i++;
      clientIdLength++;
    }
    if (clientIdLength > 65535) {
      throw new IllegalArgumentException("ClientId longer than 65535 characters");
    }

    MqttConnectOptions.validateURI(serverURI);

    this.serverURI = serverURI;
    this.clientId = clientId;

    this.persistence = persistence;
    if (this.persistence == null) {
      this.persistence = new MemoryPersistence();
    }

    // @TRACE 101=<init> ClientID={0} ServerURI={1} PersistenceType={2}
    log.fine(CLASS_NAME, methodName, "101", new Object[] {clientId, serverURI, persistence});

    this.persistence.open(clientId, serverURI);
    this.comms = new ClientComms(this, this.persistence, pingSender);
    this.persistence.close();
    this.topics = new Hashtable();
  }