Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    *  Licensed to the Apache Software Foundation (ASF) under one or more
    *  contributor license agreements.  See the NOTICE file distributed with
    *  this work for additional information regarding copyright ownership.
    *  The ASF licenses this file to You under the Apache License, Version 2.0
    *  (the "License"); you may not use this file except in compliance with
    *  the License.  You may obtain a copy of the License at
    *
    *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  package org.apache.tomcat.websocket;
  
  import static org.jboss.web.WebsocketsMessages.MESSAGES;
  
  import java.io.Writer;
  import java.util.List;
  import java.util.Queue;
  
  
  
  public abstract class WsRemoteEndpointImplBase implements RemoteEndpoint {
  
      // Milliseconds so this is 20 seconds
      private static final long DEFAULT_BLOCKING_SEND_TIMEOUT = 20 * 1000;
  
      public static final String BLOCKING_SEND_TIMEOUT_PROPERTY =
              "org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT";
  
      private final StateMachine stateMachine = new StateMachine();
  
              new IntermediateMessageHandler(this);
  
      private Transformation transformation = null;
      private boolean messagePartInProgress = false;
      private final Queue<MessagePartmessagePartQueue = new ArrayDeque<MessagePart>();
      private final Object messagePartLock = new Object();
  
      // State
      private volatile boolean closed = false;
      private boolean fragmented = false;
      private boolean nextFragmented = false;
      private boolean text = false;
      private boolean nextText = false;
  
      // Max size of WebSocket header is 14 bytes
      private final ByteBuffer headerBuffer = ByteBuffer.allocate(14);
      private final ByteBuffer outputBuffer = ByteBuffer.allocate(8192);
      private final CharsetEncoder encoder = new Utf8Encoder();
      private final ByteBuffer encoderBuffer = ByteBuffer.allocate(8192);
      private final AtomicBoolean batchingAllowed = new AtomicBoolean(false);
      private volatile long sendTimeout = -1;
      private WsSession wsSession;
      private List<EncoderEntryencoderEntries = new ArrayList<EncoderEntry>();
  
  
      protected void setTransformation(Transformation transformation) {
          this. = transformation;
      }
  
  
      public long getSendTimeout() {
          return ;
      }
  
  
      public void setSendTimeout(long timeout) {
          this. = timeout;
      }
  
  
     @Override
     public void setBatchingAllowed(boolean batchingAllowedthrows IOException {
         boolean oldValue = this..getAndSet(batchingAllowed);
 
         if (oldValue && !batchingAllowed) {
             flushBatch();
         }
     }
 
 
     @Override
     public boolean getBatchingAllowed() {
         return .get();
     }
 
 
     @Override
     public void flushBatch() throws IOException {
         startMessageBlock(.nulltrue);
     }
 
 
     public void sendBytes(ByteBuffer datathrows IOException {
         if (data == null) {
             throw .invalidNullData();
         }
         .binaryStart();
         startMessageBlock(.datatrue);
         .complete(true);
     }
 
 
     public Future<VoidsendBytesByFuture(ByteBuffer data) {
         FutureToSendHandler f2sh = new FutureToSendHandler();
         sendBytesByCompletion(dataf2sh);
         return f2sh;
     }
 
 
     public void sendBytesByCompletion(ByteBuffer dataSendHandler handler) {
         if (data == null) {
             throw .invalidNullData();
         }
         if (handler == null) {
             throw .invalidNullHandler();
         }
         StateUpdateSendHandler sush = new StateUpdateSendHandler(handler);
         .binaryStart();
         startMessage(.datatruesush);
     }
 
 
     public void sendPartialBytes(ByteBuffer partialByteboolean last)
             throws IOException {
         if (partialByte == null) {
             throw .invalidNullData();
         }
         .binaryPartialStart();
         startMessageBlock(.partialBytelast);
         .complete(last);
     }
 
 
     @Override
     public void sendPing(ByteBuffer applicationDatathrows IOException,
             IllegalArgumentException {
         startMessageBlock(.applicationDatatrue);
     }
 
 
     @Override
     public void sendPong(ByteBuffer applicationDatathrows IOException,
             IllegalArgumentException {
         startMessageBlock(.applicationDatatrue);
     }
 
 
     public void sendString(String textthrows IOException {
         if (text == null) {
             throw .invalidNullData();
         }
         .textStart();
         sendPartialString(CharBuffer.wrap(text), true);
     }
 
 
     public Future<VoidsendStringByFuture(String text) {
         FutureToSendHandler f2sh = new FutureToSendHandler();
         sendStringByCompletion(textf2sh);
         return f2sh;
     }
 
 
     public void sendStringByCompletion(String textSendHandler handler) {
         if (text == null) {
             throw .invalidNullData();
         }
         if (handler == null) {
             throw .invalidNullHandler();
         }
         .textStart();
         TextMessageSendHandler tmsh = new TextMessageSendHandler(handler,
                 CharBuffer.wrap(text), truethis);
         tmsh.write();
         // TextMessageSendHandler will update stateMachine when it completes
     }
 
 
     public void sendPartialString(String fragmentboolean isLast)
             throws IOException {
         if (fragment == null) {
             throw .invalidNullData();
         }
         .textPartialStart();
         sendPartialString(CharBuffer.wrap(fragment), isLast);
     }
 
 
     public OutputStream getSendStream() {
         .streamStart();
         return new WsOutputStream(this);
     }
 
 
     public Writer getSendWriter() {
         .writeStart();
         return new WsWriter(this);
     }
 
 
     void sendPartialString(CharBuffer partboolean lastthrows IOException {
         try {
             // Get the timeout before we send the message. The message may
             // trigger a session close and depending on timing the client
             // session may close before we can read the timeout.
             long timeout = getBlockingSendTimeout();
             FutureToSendHandler f2sh = new FutureToSendHandler();
             TextMessageSendHandler tmsh = new TextMessageSendHandler(f2shpart,
                     lastthis);
             tmsh.write();
             if (timeout == -1) {
                 f2sh.get();
             } else {
                 f2sh.get(timeout.);
             }
         } catch (InterruptedException e) {
             throw new IOException(e);
         } catch (ExecutionException e) {
             throw new IOException(e);
         } catch (TimeoutException e) {
             throw new IOException(e);
         }
     }
 
 
     void startMessageBlock(byte opCodeByteBuffer payloadboolean last)
             throws IOException {
         // Get the timeout before we send the message. The message may
         // trigger a session close and depending on timing the client
         // session may close before we can read the timeout.
         long timeout = getBlockingSendTimeout();
         FutureToSendHandler f2sh = new FutureToSendHandler();
         startMessage(opCodepayloadlastf2sh);
         try {
             if (timeout == -1) {
                 f2sh.get();
             } else {
                 f2sh.get(timeout.);
             }
             // FIXME: maybe not needed
             if (payload != null) {
                 payload.clear();
             }
         } catch (InterruptedException e) {
             throw new IOException(e);
         } catch (ExecutionException e) {
             throw new IOException(e);
         } catch (TimeoutException e) {
             throw new IOException(e);
         }
     }
 
 
     void startMessage(byte opCodeByteBuffer payloadboolean last,
             SendHandler handler) {
 
         .updateLastActive();
 
         List<MessagePartmessageParts = new ArrayList<MessagePart>();
         messageParts.add(new MessagePart(last, 0, opCodepayload,
                 ,
                 new EndMessageHandler(thishandler)));
 
         messageParts = .sendMessagePart(messageParts);
 
         // Some extensions/transformations may buffer messages so it is possible
         // that no message parts will be returned. If this is the case the
         // trigger the suppler SendHandler
         if (messageParts.size() == 0) {
             handler.onResult(new SendResult());
             return;
         }
 
         MessagePart mp = messageParts.remove(0);
 
         boolean doWrite = false;
         synchronized () {
             if (. == mp.getOpCode() && getBatchingAllowed()) {
                 // Should not happen. To late to send batched messages now since
                 // the session has been closed. Complain loudly.
                 ..cannotFlushOnClose();
             }
             if () {
                 // When a control message is sent while another message is being
                 // sent, the control message is queued. Chances are the
                 // subsequent data message part will end up queued while the
                 // control message is sent. The logic in this class (state
                 // machine, EndMessageHandler, TextMessageSendHandler) ensures
                 // that there will only ever be one data message part in the
                 // queue. There could be multiple control messages in the queue.
 
                 // Add it to the queue
                 .add(mp);
             } else {
                  = true;
                 doWrite = true;
             }
             // Add any remaining messages to the queue
             .addAll(messageParts);
         }
         if (doWrite) {
             // Actual write has to be outside sync block to avoid possible
             // deadlock between messagePartLock and writeLock in
             // o.a.coyote.http11.upgrade.AbstractServletOutputStream
             writeMessagePart(mp);
         }
     }
 
 
     void endMessage(SendHandler handlerSendResult result) {
         boolean doWrite = false;
         MessagePart mpNext = null;
         synchronized () {
 
              = ;
              = ;
 
             mpNext = .poll();
             if (mpNext == null) {
                  = false;
             } else if (!){
                 // Session may have been closed unexpectedly in the middle of
                 // sending a fragmented message closing the endpoint. If this
                 // happens, clearly there is no point trying to send the rest of
                 // the message.
                 doWrite = true;
             }
         }
         if (doWrite) {
             // Actual write has to be outside sync block to avoid possible
             // deadlock between messagePartLock and writeLock in
             // o.a.coyote.http11.upgrade.AbstractServletOutputStream
             writeMessagePart(mpNext);
         }
 
         .updateLastActive();
 
         // Some handlers, such as the IntermediateMessageHandler, do not have a
         // nested handler so handler may be null.
         if (handler != null) {
             handler.onResult(result);
         }
     }
 
 
     void writeMessagePart(MessagePart mp) {
         if () {
             throw .messageSessionClosed();
         }
 
         if (. == mp.getOpCode()) {
              = ;
              = ;
             .flip();
             SendHandler flushHandler = new OutputBufferFlushSendHandler(
                     mp.getEndHandler());
             doWrite(flushHandler);
             return;
         }
 
         // Control messages may be sent in the middle of fragmented message
         // so they have no effect on the fragmented or text flags
         boolean first;
         if (Util.isControl(mp.getOpCode())) {
              = ;
              = ;
             if (mp.getOpCode() == .) {
                  = true;
             }
             first = true;
         } else {
             boolean isText = Util.isText(mp.getOpCode());
 
             if () {
                 // Currently fragmented
                 if ( != isText) {
                     throw .messageFragmentTypeChange();
                 }
                  = ;
                  = !mp.isFin();
                 first = false;
             } else {
                 // Wasn't fragmented. Might be now
                 if (mp.isFin()) {
                      = false;
                 } else {
                      = true;
                      = isText;
                 }
                 first = true;
             }
         }
 
         byte[] mask;
 
         if (isMasked()) {
             mask = Util.generateMask();
         } else {
             mask = null;
         }
 
         .clear();
         writeHeader(mp.isFin(), mp.getRsv(), mp.getOpCode(),
                 isMasked(), mp.getPayload(), maskfirst);
         .flip();
 
         if (getBatchingAllowed() || isMasked()) {
             // Need to write via output buffer
             OutputBufferSendHandler obsh = new OutputBufferSendHandler(
                     mp.getEndHandler(), mp.getPayload(), mask,
                     , !getBatchingAllowed(), this);
             obsh.write();
         } else {
             // Can write directly
             doWrite(mp.getEndHandler(), mp.getPayload());
         }
     }
 
 
     private long getBlockingSendTimeout() {
         Object obj = .getUserProperties().get(
                 );
         Long userTimeout = null;
         if (obj instanceof Long) {
             userTimeout = (Longobj;
         }
         if (userTimeout == null) {
             return ;
         } else {
             return userTimeout.longValue();
         }
     }


    
Wraps the user provided handler so that the end point is notified when the message is complete.
 
     private static class EndMessageHandler implements SendHandler {
 
         private final WsRemoteEndpointImplBase endpoint;
         private final SendHandler handler;
 
         public EndMessageHandler(WsRemoteEndpointImplBase endpoint,
                 SendHandler handler) {
             this. = endpoint;
             this. = handler;
         }
 
 
         @Override
         public void onResult(SendResult result) {
             .endMessage(result);
         }
     }


    
If a transformation needs to split a MessagePart into multiple MessageParts, it uses this handler as the end handler for each of the additional MessageParts. This handler notifies this this class that the MessagePart has been processed and that the next MessagePart in the queue should be started. The final MessagePart will use the WsRemoteEndpointImplBase.EndMessageHandler provided with the original MessagePart.
 
     private static class IntermediateMessageHandler implements SendHandler {
 
         private final WsRemoteEndpointImplBase endpoint;
 
         public IntermediateMessageHandler(WsRemoteEndpointImplBase endpoint) {
             this. = endpoint;
         }
 
 
         @Override
         public void onResult(SendResult result) {
             .endMessage(nullresult);
         }
     }
 
 
     public void sendObject(Object objthrows IOExceptionEncodeException {
         Future<Voidf = sendObjectByFuture(obj);
         try {
             f.get();
         } catch (InterruptedException e) {
             throw new IOException(e);
         } catch (ExecutionException e) {
             Throwable cause = e.getCause();
             if (cause instanceof IOException) {
                 throw (IOExceptioncause;
             } else if (cause instanceof EncodeException) {
                 throw (EncodeExceptioncause;
             } else {
                 throw new IOException(e);
             }
         }
     }
 
     public Future<VoidsendObjectByFuture(Object obj) {
         FutureToSendHandler f2sh = new FutureToSendHandler();
         sendObjectByCompletion(objf2sh);
         return f2sh;
     }
 
 
     @SuppressWarnings({"unchecked""rawtypes"})
     public void sendObjectByCompletion(Object objSendHandler completion) {
 
         if (obj == null) {
             throw .invalidNullData();
         }
         if (completion == null) {
             throw .invalidNullHandler();
         }
 
         if (Util.isPrimitive(obj.getClass())) {
             String msg = obj.toString();
             sendStringByCompletion(msgcompletion);
             return;
         }
 
         Encoder encoder = findEncoder(obj);
 
         try {
             if (encoder instanceof Encoder.Text) {
                 String msg = ((Encoder.Textencoder).encode(obj);
                 sendStringByCompletion(msgcompletion);
             } else if (encoder instanceof Encoder.TextStream) {
                 Writer w = null;
                 try {
                     w = getSendWriter();
                     ((Encoder.TextStreamencoder).encode(objw);
                 } finally {
                     if (w != null) {
                         try {
                             w.close();
                         } catch (IOException ioe) {
                             // Ignore
                         }
                     }
                 }
                 completion.onResult(new SendResult());
             } else if (encoder instanceof Encoder.Binary) {
                 ByteBuffer msg = ((Encoder.Binaryencoder).encode(obj);
                 sendBytesByCompletion(msgcompletion);
             } else if (encoder instanceof Encoder.BinaryStream) {
                 OutputStream os = null;
                 try {
                     os = getSendStream();
                     ((Encoder.BinaryStreamencoder).encode(objos);
                 } finally {
                     if (os != null) {
                         try {
                             os.close();
                         } catch (IOException ioe) {
                             // Ignore
                         }
                     }
                 }
                 completion.onResult(new SendResult());
             } else {
                 throw new EncodeException(obj.noEncoderForClass(obj.getClass().getName()));
             }
         } catch (EncodeException e) {
             SendResult sr = new SendResult(e);
             completion.onResult(sr);
         } catch (IOException e) {
             SendResult sr = new SendResult(e);
             completion.onResult(sr);
         }
     }
 
 
     protected void setSession(WsSession wsSession) {
         this. = wsSession;
     }
 
 
     protected void setEncoders(EndpointConfig endpointConfig)
             throws DeploymentException {
         .clear();
         for (Class<? extends EncoderencoderClazz :
                 endpointConfig.getEncoders()) {
             Encoder instance;
             try {
                 instance = encoderClazz.newInstance();
                 instance.init(endpointConfig);
             } catch (InstantiationException e) {
                 throw new DeploymentException(
                         .cannotInstatiateEncoder(encoderClazz.getName()), e);
             } catch (IllegalAccessException e) {
                 throw new DeploymentException(
                         .cannotInstatiateEncoder(encoderClazz.getName()), e);
             }
             EncoderEntry entry = new EncoderEntry(
                     Util.getEncoderType(encoderClazz), instance);
             .add(entry);
         }
     }
 
 
     private Encoder findEncoder(Object obj) {
         for (EncoderEntry entry : ) {
             if (entry.getClazz().isAssignableFrom(obj.getClass())) {
                 return entry.getEncoder();
             }
         }
         return null;
     }
 
 
     public final void close() {
         for (EncoderEntry entry : ) {
             entry.getEncoder().destroy();
         }
         doClose();
     }
 
 
     protected abstract void doWrite(SendHandler handlerByteBuffer... data);
     protected abstract boolean isMasked();
     protected abstract void doClose();
 
     private static void writeHeader(ByteBuffer headerBufferboolean fin,
             int rsvbyte opCodeboolean maskedByteBuffer payload,
             byte[] maskboolean first) {
 
         byte b = 0;
 
         if (fin) {
             // Set the fin bit
             b -= 128;
         }
 
         b += (rsv << 4);
 
         if (first) {
             // This is the first fragment of this message
             b += opCode;
         }
         // If not the first fragment, it is a continuation with opCode of zero
 
         headerBuffer.put(b);
 
         if (masked) {
             b = (byte) 0x80;
         } else {
             b = 0;
         }
 
         // Next write the mask && length length
         if (payload.limit() < 126) {
             headerBuffer.put((byte) (payload.limit() | b));
         } else if (payload.limit() < 65536) {
             headerBuffer.put((byte) (126 | b));
             headerBuffer.put((byte) (payload.limit() >>> 8));
             headerBuffer.put((byte) (payload.limit() & 0xFF));
         } else {
             // Will never be more than 2^31-1
             headerBuffer.put((byte) (127 | b));
             headerBuffer.put((byte) 0);
             headerBuffer.put((byte) 0);
             headerBuffer.put((byte) 0);
             headerBuffer.put((byte) 0);
             headerBuffer.put((byte) (payload.limit() >>> 24));
             headerBuffer.put((byte) (payload.limit() >>> 16));
             headerBuffer.put((byte) (payload.limit() >>> 8));
             headerBuffer.put((byte) (payload.limit() & 0xFF));
         }
         if (masked) {
             headerBuffer.put(mask[0]);
             headerBuffer.put(mask[1]);
             headerBuffer.put(mask[2]);
             headerBuffer.put(mask[3]);
         }
     }
 
 
     private class TextMessageSendHandler implements SendHandler {
 
         private final SendHandler handler;
         private final CharBuffer message;
         private final boolean isLast;
         private final CharsetEncoder encoder;
         private final ByteBuffer buffer;
         private final WsRemoteEndpointImplBase endpoint;
         private volatile boolean isDone = false;
 
         public TextMessageSendHandler(SendHandler handlerCharBuffer message,
                 boolean isLastCharsetEncoder encoder,
                 ByteBuffer encoderBufferWsRemoteEndpointImplBase endpoint) {
             this. = handler;
             this. = message;
             this. = isLast;
             this. = encoder.reset();
             this. = encoderBuffer;
             this. = endpoint;
         }
 
         public void write() {
             // FIXME: maybe not needed
             synchronized () {
                 .clear();
                 CoderResult cr = .encode(true);
                 if (cr.isError()) {
                     throw new IllegalArgumentException(cr.toString());
                 }
                  = !cr.isOverflow();
                 .flip();
                 .startMessage(.,
                          && this);
             }
         }
 
         @Override
         public void onResult(SendResult result) {
             if () {
                 ..complete();
                 .onResult(result);
             } else if(!result.isOK()) {
                 .onResult(result);
             } else if (){
                 SendResult sr = new SendResult(new IOException(.messageRemainderSessionClosed()));
                 .onResult(sr);
             } else {
                 write();
             }
         }
     }


    
Used to write data to the output buffer, flushing the buffer if it fills up.
 
     private static class OutputBufferSendHandler implements SendHandler {
 
         private final SendHandler handler;
         private final ByteBuffer headerBuffer;
         private final ByteBuffer payload;
         private final byte[] mask;
         private final ByteBuffer outputBuffer;
         private final boolean flushRequired;
         private final WsRemoteEndpointImplBase endpoint;
         private int maskIndex = 0;
 
         public OutputBufferSendHandler(SendHandler completion,
                 ByteBuffer headerBufferByteBuffer payloadbyte[] mask,
                 ByteBuffer outputBufferboolean flushRequired,
                 WsRemoteEndpointImplBase endpoint) {
             this. = completion;
             this. = headerBuffer;
             this. = payload;
             this. = mask;
             this. = outputBuffer;
             this. = flushRequired;
             this. = endpoint;
         }
 
         public void write() {
             // Write the header
             while (.hasRemaining() && .hasRemaining()) {
                 .put(.get());
             }
             if (.hasRemaining()) {
                 // Still more headers to write, need to flush
                 .flip();
                 .doWrite(this);
                 return;
             }
 
             // Write the payload
             int payloadLeft = .remaining();
             int payloadLimit = .limit();
             int outputSpace = .remaining();
             int toWrite = payloadLeft;
 
             if (payloadLeft > outputSpace) {
                 toWrite = outputSpace;
                 // Temporarily reduce the limit
                 .limit(.position() + toWrite);
             }
 
             if ( == null) {
                 // Use a bulk copy
                 .put();
             } else {
                 for (int i = 0; i < toWritei++) {
                     .put(
                             (byte) (.get() ^ ([++] & 0xFF)));
                     if ( > 3) {
                          = 0;
                     }
                 }
             }
 
             if (payloadLeft > outputSpace) {
                 // Restore the original limit
                 .limit(payloadLimit);
                 // Still more headers to write, need to flush
                 .flip();
                 .doWrite(this);
                 return;
             }
 
             if () {
                 .flip();
                 if (.remaining() == 0) {
                     .onResult(new SendResult());
                 } else {
                     .doWrite(this);
                 }
             } else {
                 .onResult(new SendResult());
             }
         }
 
         // ------------------------------------------------- SendHandler methods
         @Override
         public void onResult(SendResult result) {
             if (result.isOK()) {
                 if (.hasRemaining()) {
                     .doWrite(this);
                 } else {
                     .clear();
                     write();
                 }
             } else {
                 .onResult(result);
             }
         }
     }


    
Ensures that the output buffer is cleared after it has been flushed.
 
     private static class OutputBufferFlushSendHandler implements SendHandler {
 
         private final ByteBuffer outputBuffer;
         private final SendHandler handler;
 
         public OutputBufferFlushSendHandler(ByteBuffer outputBufferSendHandler handler) {
             this. = outputBuffer;
             this. = handler;
         }
 
         @Override
         public void onResult(SendResult result) {
             if (result.isOK()) {
                 .clear();
             }
             .onResult(result);
         }
     }
 
 
     private class WsOutputStream extends OutputStream {
 
         private final WsRemoteEndpointImplBase endpoint;
         private final ByteBuffer buffer = ByteBuffer.allocate(8192);
         private final Object closeLock = new Object();
         private volatile boolean closed = false;
 
         public WsOutputStream(WsRemoteEndpointImplBase endpoint) {
             this. = endpoint;
         }
 
         @Override
         public void write(int bthrows IOException {
             if () {
                 throw .closedOutputStream();
             }
 
             if (.remaining() == 0) {
                 flush();
             }
             .put((byteb);
         }
 
         @Override
         public void write(byte[] bint offint lenthrows IOException {
             if () {
                 throw .closedOutputStream();
             }
             if (len == 0) {
                 return;
             }
             if ((off < 0) || (off > b.length) || (len < 0) ||
                 ((off + len) > b.length) || ((off + len) < 0)) {
                 throw new IndexOutOfBoundsException();
             }
 
             if (.remaining() == 0) {
                 flush();
             }
             int remaining = .remaining();
             int written = 0;
 
             while (remaining < len - written) {
                 .put(boff + writtenremaining);
                 written += remaining;
                 flush();
                 remaining = .remaining();
             }
             .put(boff + writtenlen - written);
         }
 
         @Override
         public void flush() throws IOException {
             if () {
                 throw .closedOutputStream();
             }
 
             doWrite(false);
         }
 
         @Override
         public void close() throws IOException {
             synchronized () {
                 if () {
                     return;
                 }
                  = true;
             }
 
             doWrite(true);
         }
 
         private void doWrite(boolean lastthrows IOException {
             .flip();
             .startMessageBlock(.last);
             .complete(last);
             .clear();
         }
     }
 
 
     private static class WsWriter extends Writer {
 
         private final WsRemoteEndpointImplBase endpoint;
         private final CharBuffer buffer = CharBuffer.allocate(8192);
         private final Object closeLock = new Object();
         private volatile boolean closed = false;
 
         public WsWriter(WsRemoteEndpointImplBase endpoint) {
             this. = endpoint;
         }
 
         @Override
         public void write(char[] cbufint offint lenthrows IOException {
             if () {
                 throw .closedWriter();
             }
             if (len == 0) {
                 return;
             }
             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                     ((off + len) > cbuf.length) || ((off + len) < 0)) {
                 throw new IndexOutOfBoundsException();
             }
 
             if (.remaining() == 0) {
                 flush();
             }
             int remaining = .remaining();
             int written = 0;
 
            while (remaining < len - written) {
                .put(cbufoff + writtenremaining);
                written += remaining;
                flush();
                remaining = .remaining();
            }
            .put(cbufoff + writtenlen - written);
        }
        @Override
        public void flush() throws IOException {
            if () {
                throw .closedWriter();
            }
            doWrite(false);
        }
        @Override
        public void close() throws IOException {
            synchronized () {
                if () {
                    return;
                }
                 = true;
            }
            doWrite(true);
        }
        private void doWrite(boolean lastthrows IOException {
            .flip();
            .sendPartialString(last);
            .clear();
        }
    }