/**
   * Adds the data to the buffer for binary data. If a textual message is currently in progress that
   * message will be completed and a new binary message started. If the buffer for binary data is
   * full, the buffer will be flushed and a new binary continuation fragment started.
   *
   * @param b The byte (only the least significant byte is used) of data to send to the client.
   * @throws IOException If a flush is required and an error occurs writing the WebSocket frame to
   *     the client
   */
  public void writeBinaryData(int b) throws IOException {
    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (bb.position() == bb.capacity()) {
          doFlush(false);
        }
        if (text == null) {
          text = Boolean.FALSE;
        } else if (text == Boolean.TRUE) {
          // Flush the character data
          flush();
          text = Boolean.FALSE;
        }
        bb.put((byte) (b & 0xFF));
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
  /**
   * Generic function to send either a ping or a pong.
   *
   * @param data Optional message.
   * @param opcode The byte to include as the opcode.
   * @throws IOException If an error occurs writing to the client
   */
  private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        doFlush(false);

        upgradeOutbound.write(0x80 | opcode);
        if (data == null) {
          upgradeOutbound.write(0);
        } else {
          upgradeOutbound.write(data.limit() - data.position());
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }

        upgradeOutbound.flush();
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
  /**
   * Flush any message (binary or textual) that may be buffered and then send a WebSocket text
   * message as a single frame with the provided buffer as the payload of the message.
   *
   * @param msgCb The buffer containing the payload
   * @throws IOException If an error occurs writing to the client
   */
  public void writeTextMessage(CharBuffer msgCb) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (text != null) {
          // Empty the buffer
          flush();
        }
        text = Boolean.TRUE;
        doWriteText(msgCb, true);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
 /**
  * Flush any message (binary or textual) that may be buffered.
  *
  * @throws IOException If an error occurs writing to the client
  */
 public void flush() throws IOException {
   try {
     synchronized (stateLock) {
       if (closed) {
         throw new IOException(sm.getString("outbound.closed"));
       }
       doFlush(true);
     }
   } catch (IOException ioe) {
     // Any IOException is terminal. Make sure the Inbound side knows
     // that something went wrong.
     // The exception handling needs to be outside of the sync to avoid
     // possible deadlocks (e.g. BZ55524) when triggering the inbound
     // close as that will execute user code
     streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
     throw ioe;
   }
 }
  /**
   * Send a close message to the client
   *
   * @param status Must be a valid status code or zero to send no code
   * @param data Optional message. If message is defined, a valid status code must be provided.
   * @throws IOException If an error occurs writing to the client
   */
  public void close(int status, ByteBuffer data) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          return;
        }

        // Send any partial data we have
        try {
          doFlush(false);
        } finally {
          closed = true;
        }

        upgradeOutbound.write(0x88);
        if (status == 0) {
          upgradeOutbound.write(0);
        } else if (data == null || data.position() == data.limit()) {
          upgradeOutbound.write(2);
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
        } else {
          upgradeOutbound.write(2 + data.limit() - data.position());
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }
        upgradeOutbound.flush();

        bb = null;
        cb = null;
        upgradeOutbound = null;
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }