content: package org.java_websocket;
  
  import java.io.IOException;
  import java.net.InetSocketAddress;
  import java.net.Socket;
  import java.nio.ByteBuffer;
  import java.nio.channels.ByteChannel;
  import java.nio.channels.NotYetConnectedException;
  import java.nio.channels.SelectionKey;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.List;
  import java.util.concurrent.BlockingQueue;
  import java.util.concurrent.LinkedBlockingQueue;
  
  import org.java_websocket.drafts.Draft;
  import org.java_websocket.drafts.Draft.CloseHandshakeType;
  import org.java_websocket.drafts.Draft.HandshakeState;
  import org.java_websocket.drafts.Draft_10;
  import org.java_websocket.drafts.Draft_17;
  import org.java_websocket.drafts.Draft_75;
  import org.java_websocket.drafts.Draft_76;
  import org.java_websocket.exceptions.IncompleteHandshakeException;
  import org.java_websocket.exceptions.InvalidDataException;
  import org.java_websocket.exceptions.InvalidHandshakeException;
  import org.java_websocket.exceptions.WebsocketNotConnectedException;
  import org.java_websocket.framing.CloseFrame;
  import org.java_websocket.framing.CloseFrameBuilder;
  import org.java_websocket.framing.Framedata;
  import org.java_websocket.framing.Framedata.Opcode;
  import org.java_websocket.handshake.ClientHandshake;
  import org.java_websocket.handshake.ClientHandshakeBuilder;
  import org.java_websocket.handshake.Handshakedata;
  import org.java_websocket.handshake.ServerHandshake;
  import org.java_websocket.handshake.ServerHandshakeBuilder;
  import org.java_websocket.server.WebSocketServer.WebSocketWorker;
  import org.java_websocket.util.Charsetfunctions;
  
  /**
   * Represents one end (client or server) of a single WebSocketImpl connection.
   * Takes care of the "handshake" phase, then allows for easy sending of
   * text frames, and receiving frames through an event-based model.
   * 
   */
  public class WebSocketImpl implements WebSocket {
  
  	public static int RCVBUF = 16384;
  
  	public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization
  
  	public static final List<Draft> defaultdraftlist = new ArrayList<Draft>( 4 );
  	static {
  		defaultdraftlist.add( new Draft_17() );
  		defaultdraftlist.add( new Draft_10() );
  		defaultdraftlist.add( new Draft_76() );
  		defaultdraftlist.add( new Draft_75() );
  	}
  
  	public SelectionKey key;
  
  	/** the possibly wrapped channel object whose selection is controlled by {@link #key} */
  	public ByteChannel channel;
  	/**
  	 * Queue of buffers that need to be sent to the client.
  	 */
  	public final BlockingQueue<ByteBuffer> outQueue;
  	/**
  	 * Queue of buffers that need to be processed
  	 */
  	public final BlockingQueue<ByteBuffer> inQueue;
  
  	/**
  	 * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
  	 **/
  	public volatile WebSocketWorker workerThread; // TODO reset worker?
  
  	/** When true no further frames may be submitted to be sent */
  	private volatile boolean flushandclosestate = false;
  
  	private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED;
  
  	/**
  	 * The listener to notify of WebSocket events.
  	 */
  	private final WebSocketListener wsl;
  
  	private List<Draft> knownDrafts;
  
  	private Draft draft = null;
  
  	private Role role;
  
  	private Opcode current_continuous_frame_opcode = null;
  
  	/** the bytes of an incomplete received handshake */
  	private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 );
  
  	/** stores the handshake sent by this websocket ( Role.CLIENT only ) */
  	private ClientHandshake handshakerequest = null;
  
  	private String closemessage = null;
  	private Integer closecode = null;
  	private Boolean closedremotely = null;
  	
  	private String resourceDescriptor = null;
  
  	/**
  	 * crates a websocket with server role
  	 */
  	public WebSocketImpl( WebSocketListener listener , List<Draft> drafts ) {
  		this( listener, (Draft) null );
  		this.role = Role.SERVER;
  		// draft.copyInstance will be called when the draft is first needed
  		if( drafts == null || drafts.isEmpty() ) {
  			knownDrafts = defaultdraftlist;
  		} else {
  			knownDrafts = drafts;
  		}
  	}
  
  	/**
  	 * crates a websocket with client role
  	 * 
  	 * @param socket
  	 *            may be unbound
  	 */
  	public WebSocketImpl( WebSocketListener listener , Draft draft ) {
  		if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel
  			throw new IllegalArgumentException( "parameters must not be null" );
  		this.outQueue = new LinkedBlockingQueue<ByteBuffer>();
  		inQueue = new LinkedBlockingQueue<ByteBuffer>();
  		this.wsl = listener;
  		this.role = Role.CLIENT;
  		if( draft != null )
  			this.draft = draft.copyInstance();
  	}
  
  	@Deprecated
  	public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) {
  		this( listener, draft );
  	}
  
  	@Deprecated
  	public WebSocketImpl( WebSocketListener listener , List<Draft> drafts , Socket socket ) {
  		this( listener, drafts );
  	}
  
  	/**
  	 * 
  	 */
  	public void decode( ByteBuffer socketBuffer ) {
  		assert ( socketBuffer.hasRemaining() );
  
  		if( DEBUG )
  			System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" );
  
  		if( readystate != READYSTATE.NOT_YET_CONNECTED ) {
  			decodeFrames( socketBuffer );;
  		} else {
  			if( decodeHandshake( socketBuffer ) ) {
  				assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time
  
  				if( socketBuffer.hasRemaining() ) {
  					decodeFrames( socketBuffer );
  				} else if( tmpHandshakeBytes.hasRemaining() ) {
  					decodeFrames( tmpHandshakeBytes );
  				}
  			}
  		}
  		assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() );
  	}
  	/**
  	 * Returns whether the handshake phase has is completed.
  	 * In case of a broken handshake this will be never the case.
  	 **/
  	private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
  		ByteBuffer socketBuffer;
  		if( tmpHandshakeBytes.capacity() == 0 ) {
  			socketBuffer = socketBufferNew;
  		} else {
  			if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) {
  				ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() );
  				tmpHandshakeBytes.flip();
  				buf.put( tmpHandshakeBytes );
  				tmpHandshakeBytes = buf;
  			}
  
  			tmpHandshakeBytes.put( socketBufferNew );
  			tmpHandshakeBytes.flip();
  			socketBuffer = tmpHandshakeBytes;
  		}
  		socketBuffer.mark();
  		try {
  			if( draft == null ) {
  				HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer );
  				if( isflashedgecase == HandshakeState.MATCHED ) {
  					try {
  						write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) );
  						close( CloseFrame.FLASHPOLICY, "" );
  					} catch ( InvalidDataException e ) {
  						close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true );
  					}
  					return false;
  				}
  			}
  			HandshakeState handshakestate = null;
  
  			try {
  				if( role == Role.SERVER ) {
  					if( draft == null ) {
  						for( Draft d : knownDrafts ) {
  							d = d.copyInstance();
  							try {
  								d.setParseMode( role );
  								socketBuffer.reset();
  								Handshakedata tmphandshake = d.translateHandshake( socketBuffer );
  								if( tmphandshake instanceof ClientHandshake == false ) {
  									flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
  									return false;
  								}
  								ClientHandshake handshake = (ClientHandshake) tmphandshake;
  								handshakestate = d.acceptHandshakeAsServer( handshake );
  								if( handshakestate == HandshakeState.MATCHED ) {
  									resourceDescriptor = handshake.getResourceDescriptor();
  									ServerHandshakeBuilder response;
  									try {
  										response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake );
  									} catch ( InvalidDataException e ) {
  										flushAndClose( e.getCloseCode(), e.getMessage(), false );
  										return false;
  									} catch ( RuntimeException e ) {
  										wsl.onWebsocketError( this, e );
  										flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
  										return false;
  									}
  									write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) );
  									draft = d;
  									open( handshake );
  									return true;
  								}
  							} catch ( InvalidHandshakeException e ) {
  								// go on with an other draft
  							}
  						}
  						if( draft == null ) {
  							close( CloseFrame.PROTOCOL_ERROR, "no draft matches" );
  						}
  						return false;
  					} else {
  						// special case for multiple step handshakes
  						Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
  						if( tmphandshake instanceof ClientHandshake == false ) {
  							flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
  							return false;
  						}
  						ClientHandshake handshake = (ClientHandshake) tmphandshake;
  						handshakestate = draft.acceptHandshakeAsServer( handshake );
  
  						if( handshakestate == HandshakeState.MATCHED ) {
  							open( handshake );
  							return true;
  						} else {
  							close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" );
  						}
  						return false;
  					}
  				} else if( role == Role.CLIENT ) {
  					draft.setParseMode( role );
  					Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
  					if( tmphandshake instanceof ServerHandshake == false ) {
  						flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
  						return false;
  					}
  					ServerHandshake handshake = (ServerHandshake) tmphandshake;
  					handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake );
  					if( handshakestate == HandshakeState.MATCHED ) {
  						try {
  							wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake );
  						} catch ( InvalidDataException e ) {
  							flushAndClose( e.getCloseCode(), e.getMessage(), false );
  							return false;
  						} catch ( RuntimeException e ) {
  							wsl.onWebsocketError( this, e );
  							flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
  							return false;
  						}
  						open( handshake );
  						return true;
  					} else {
  						close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" );
  					}
  				}
  			} catch ( InvalidHandshakeException e ) {
  				close( e );
  			}
  		} catch ( IncompleteHandshakeException e ) {
  			if( tmpHandshakeBytes.capacity() == 0 ) {
  				socketBuffer.reset();
  				int newsize = e.getPreferedSize();
  				if( newsize == 0 ) {
  					newsize = socketBuffer.capacity() + 16;
  				} else {
  					assert ( e.getPreferedSize() >= socketBuffer.remaining() );
  				}
  				tmpHandshakeBytes = ByteBuffer.allocate( newsize );
  
  				tmpHandshakeBytes.put( socketBufferNew );
  				// tmpHandshakeBytes.flip();
  			} else {
  				tmpHandshakeBytes.position( tmpHandshakeBytes.limit() );
  				tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() );
  			}
  		}
  		return false;
  	}
  
  	private void decodeFrames( ByteBuffer socketBuffer ) {
  
  		List<Framedata> frames;
  		try {
  			frames = draft.translateFrame( socketBuffer );
  			for( Framedata f : frames ) {
  				if( DEBUG )
  					System.out.println( "matched frame: " + f );
  				Opcode curop = f.getOpcode();
  				boolean fin = f.isFin();
  
  				if( curop == Opcode.CLOSING ) {
  					int code = CloseFrame.NOCODE;
  					String reason = "";
  					if( f instanceof CloseFrame ) {
  						CloseFrame cf = (CloseFrame) f;
  						code = cf.getCloseCode();
  						reason = cf.getMessage();
  					}
  					if( readystate == READYSTATE.CLOSING ) {
  						// complete the close handshake by disconnecting
  						closeConnection( code, reason, true );
  					} else {
  						// echo close handshake
  						if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY )
  							close( code, reason, true );
  						else
  							flushAndClose( code, reason, false );
  					}
  					continue;
  				} else if( curop == Opcode.PING ) {
  					wsl.onWebsocketPing( this, f );
  					continue;
  				} else if( curop == Opcode.PONG ) {
  					wsl.onWebsocketPong( this, f );
  					continue;
  				} else if( !fin || curop == Opcode.CONTINUOUS ) {
  					if( curop != Opcode.CONTINUOUS ) {
  						if( current_continuous_frame_opcode != null )
  							throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." );
  						current_continuous_frame_opcode = curop;
  					} else if( fin ) {
  						if( current_continuous_frame_opcode == null )
  							throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
  						current_continuous_frame_opcode = null;
  					} else if( current_continuous_frame_opcode == null ) {
  						throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
  					}
  					try {
  						wsl.onWebsocketMessageFragment( this, f );
  					} catch ( RuntimeException e ) {
  						wsl.onWebsocketError( this, e );
  					}
  
  				} else if( current_continuous_frame_opcode != null ) {
  					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." );
  				} else if( curop == Opcode.TEXT ) {
  					try {
  						wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) );
  					} catch ( RuntimeException e ) {
  						wsl.onWebsocketError( this, e );
  					}
  				} else if( curop == Opcode.BINARY ) {
  					try {
  						wsl.onWebsocketMessage( this, f.getPayloadData() );
  					} catch ( RuntimeException e ) {
  						wsl.onWebsocketError( this, e );
  					}
  				} else {
  					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" );
  				}
  			}
  		} catch ( InvalidDataException e1 ) {
  			wsl.onWebsocketError( this, e1 );
  			close( e1 );
  			return;
  		}
  	}
  
  	private void close( int code, String message, boolean remote ) {
  		if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) {
  			if( readystate == READYSTATE.OPEN ) {
  				if( code == CloseFrame.ABNORMAL_CLOSE ) {
  					assert ( remote == false );
  					readystate = READYSTATE.CLOSING;
  					flushAndClose( code, message, false );
  					return;
  				}
  				if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) {
  					try {
  						if( !remote ) {
  							try {
  								wsl.onWebsocketCloseInitiated( this, code, message );
  							} catch ( RuntimeException e ) {
  								wsl.onWebsocketError( this, e );
  							}
  						}
  						sendFrame( new CloseFrameBuilder( code, message ) );
  					} catch ( InvalidDataException e ) {
  						wsl.onWebsocketError( this, e );
  						flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false );
  					}
  				}
  				flushAndClose( code, message, remote );
  			} else if( code == CloseFrame.FLASHPOLICY ) {
  				assert ( remote );
  				flushAndClose( CloseFrame.FLASHPOLICY, message, true );
  			} else {
  				flushAndClose( CloseFrame.NEVER_CONNECTED, message, false );
  			}
  			if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR
  				flushAndClose( code, message, remote );
  			readystate = READYSTATE.CLOSING;
  			tmpHandshakeBytes = null;
  			return;
  		}
  	}
  
  	@Override
  	public void close( int code, String message ) {
  		close( code, message, false );
  	}
  
  	/**
  	 * 
  	 * @param remote
  	 *            Indicates who "generated" <code>code</code>.<br>
  	 *            <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br>
  	 *            false means this endpoint decided to send the given code,<br>
  	 *            <code>remote</code> may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br>
  	 **/
  
  	protected synchronized void closeConnection( int code, String message, boolean remote ) {
  		if( readystate == READYSTATE.CLOSED ) {
  			return;
  		}
  
  		if( key != null ) {
  			// key.attach( null ); //see issue #114
  			key.cancel();
  		}
  		if( channel != null ) {
  			try {
  				channel.close();
  			} catch ( IOException e ) {
  				wsl.onWebsocketError( this, e );
  			}
  		}
  		try {
  			this.wsl.onWebsocketClose( this, code, message, remote );
  		} catch ( RuntimeException e ) {
  			wsl.onWebsocketError( this, e );
  		}
  		if( draft != null )
  			draft.reset();
  		handshakerequest = null;
  
  		readystate = READYSTATE.CLOSED;
  		this.outQueue.clear();
  	}
  
  	protected void closeConnection( int code, boolean remote ) {
  		closeConnection( code, "", remote );
  	}
  
  	public void closeConnection() {
  		if( closedremotely == null ) {
  			throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" );
  		}
  		closeConnection( closecode, closemessage, closedremotely );
  	}
  
  	public void closeConnection( int code, String message ) {
  		closeConnection( code, message, false );
  	}
  
  	protected synchronized void flushAndClose( int code, String message, boolean remote ) {
  		if( flushandclosestate ) {
  			return;
  		}
  		closecode = code;
  		closemessage = message;
  		closedremotely = remote;
  
  		flushandclosestate = true;
  
  		wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection
  		try {
  			wsl.onWebsocketClosing( this, code, message, remote );
  		} catch ( RuntimeException e ) {
  			wsl.onWebsocketError( this, e );
  		}
  		if( draft != null )
  			draft.reset();
  		handshakerequest = null;
  	}
  
  	public void eot() {
  		if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) {
  			closeConnection( CloseFrame.NEVER_CONNECTED, true );
  		} else if( flushandclosestate ) {
  			closeConnection( closecode, closemessage, closedremotely );
  		} else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) {
  			closeConnection( CloseFrame.NORMAL, true );
  		} else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) {
  			if( role == Role.SERVER )
  				closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
  			else
  				closeConnection( CloseFrame.NORMAL, true );
  		} else {
  			closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
  		}
  	}
  
  	@Override
  	public void close( int code ) {
  		close( code, "", false );
  	}
  
  	public void close( InvalidDataException e ) {
  		close( e.getCloseCode(), e.getMessage(), false );
  	}
  
  	/**
  	 * Send Text data to the other end.
  	 * 
  	 * @throws IllegalArgumentException
  	 * @throws NotYetConnectedException
  	 */
  	@Override
  	public void send( String text ) throws WebsocketNotConnectedException {
  		if( text == null )
  			throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
  		send( draft.createFrames( text, role == Role.CLIENT ) );
  	}
  
  	/**
  	 * Send Binary data (plain bytes) to the other end.
  	 * 
  	 * @throws IllegalArgumentException
  	 * @throws NotYetConnectedException
  	 */
  	@Override
  	public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException {
  		if( bytes == null )
  			throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
  		send( draft.createFrames( bytes, role == Role.CLIENT ) );
  	}
  
  	@Override
  	public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException {
  		send( ByteBuffer.wrap( bytes ) );
  	}
  
  	private void send( Collection<Framedata> frames ) {
  		if( !isOpen() )
  			throw new WebsocketNotConnectedException();
  		for( Framedata f : frames ) {
  			sendFrame( f );
  		}
  	}
  
  	@Override
  	public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
  		send( draft.continuousFrame( op, buffer, fin ) );
  	}
  
  	@Override
  	public void sendFrame( Framedata framedata ) {
  		if( DEBUG )
  			System.out.println( "send frame: " + framedata );
  		write( draft.createBinaryFrame( framedata ) );
  	}
  
  	@Override
  	public boolean hasBufferedData() {
  		return !this.outQueue.isEmpty();
  	}
  
  	private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException {
  		request.mark();
  		if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) {
  			return HandshakeState.NOT_MATCHED;
  		} else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) {
  			throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length );
  		} else {
  
  			for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) {
  				if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) {
  					request.reset();
  					return HandshakeState.NOT_MATCHED;
  				}
  			}
  			return HandshakeState.MATCHED;
  		}
  	}
  
  	public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException {
  		assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once";
  
  		// Store the Handshake Request we are about to send
  		this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata );
  
  		resourceDescriptor = handshakedata.getResourceDescriptor();
  		assert( resourceDescriptor != null );
  		
  		// Notify Listener
  		try {
  			wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest );
  		} catch ( InvalidDataException e ) {
  			// Stop if the client code throws an exception
  			throw new InvalidHandshakeException( "Handshake data rejected by client." );
  		} catch ( RuntimeException e ) {
  			wsl.onWebsocketError( this, e );
  			throw new InvalidHandshakeException( "rejected because of" + e );
  		}
  
  		// Send
  		write( draft.createHandshake( this.handshakerequest, role ) );
  	}
  
  	private void write( ByteBuffer buf ) {
  		if( DEBUG )
  			System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" );
  
  		outQueue.add( buf );
  		/*try {
  			outQueue.put( buf );
  		} catch ( InterruptedException e ) {
  			write( buf );
  			Thread.currentThread().interrupt(); // keep the interrupted status
  			e.printStackTrace();
  		}*/
  		wsl.onWriteDemand( this );
  	}
  
  	private void write( List<ByteBuffer> bufs ) {
  		for( ByteBuffer b : bufs ) {
  			write( b );
  		}
  	}
  
  	private void open( Handshakedata d ) {
  		if( DEBUG )
  			System.out.println( "open using draft: " + draft.getClass().getSimpleName() );
  		readystate = READYSTATE.OPEN;
  		try {
  			wsl.onWebsocketOpen( this, d );
  		} catch ( RuntimeException e ) {
  			wsl.onWebsocketError( this, e );
  		}
  	}
  
  	@Override
  	public boolean isConnecting() {
  		assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true );
  		return readystate == READYSTATE.CONNECTING; // ifflushandclosestate
  	}
  
  	@Override
  	public boolean isOpen() {
  		assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true );
  		return readystate == READYSTATE.OPEN;
  	}
  
  	@Override
  	public boolean isClosing() {
  		return readystate == READYSTATE.CLOSING;
  	}
  
  	@Override
  	public boolean isFlushAndClose() {
  		return flushandclosestate;
  	}
  
  	@Override
  	public boolean isClosed() {
  		return readystate == READYSTATE.CLOSED;
  	}
  
  	@Override
  	public READYSTATE getReadyState() {
  		return readystate;
  	}
  
  	@Override
  	public int hashCode() {
  		return super.hashCode();
  	}
  
  	@Override
  	public String toString() {
  		return super.toString(); // its nice to be able to set breakpoints here
  	}
  
  	@Override
  	public InetSocketAddress getRemoteSocketAddress() {
  		return wsl.getRemoteSocketAddress( this );
  	}
  
  	@Override
  	public InetSocketAddress getLocalSocketAddress() {
  		return wsl.getLocalSocketAddress( this );
  	}
  
  	@Override
  	public Draft getDraft() {
  		return draft;
  	}
  
  	@Override
  	public void close() {
  		close( CloseFrame.NORMAL );
  	}
  
  	@Override
  	public String getResourceDescriptor() {
  		return resourceDescriptor;
  	}
  
  }
  