public interface WebSocket {
	public enum READYSTATE {
		NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED;
	}

	public enum Role {
		CLIENT, SERVER
	}

	/**
	 * The default port of WebSockets, as defined in the spec. If the nullary
	 * constructor is used, DEFAULT_PORT will be the port the WebSocketServer is
	 * binded to. Note that ports under 1024 usually require root permissions.
	 */
	public static final int DEFAULT_PORT = 80;

	public static final int DEFAULT_WSS_PORT = 443;

	/** Convenience function which behaves like close(CloseFrame.NORMAL) */
	public void close();

	public void close(int code);

	/**
	 * sends the closing handshake. may be send in response to an other
	 * handshake.
	 */
	public void close(int code, String message);

	/**
	 * This will close the connection immediately without a proper close
	 * handshake. The code and the message therefore won't be transfered over
	 * the wire also they will be forwarded to onClose/onWebsocketClose.
	 **/
	public abstract void closeConnection(int code, String message);

	public abstract Draft getDraft();

	/**
	 * @returns never returns null
	 */
	public abstract InetSocketAddress getLocalSocketAddress();

	/**
	 * Retrieve the WebSocket 'readyState'. This represents the state of the
	 * connection. It returns a numerical value, as per W3C WebSockets specs.
	 * 
	 * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 =
	 *         CLOSED'
	 */
	public abstract READYSTATE getReadyState();

	/**
	 * @returns never returns null
	 */
	public abstract InetSocketAddress getRemoteSocketAddress();

	/**
	 * Returns the HTTP Request-URI as defined by
	 * http://tools.ietf.org/html/rfc2616#section-5.1.2<br>
	 * If the opening handshake has not yet happened it will return null.
	 **/
	public abstract String getResourceDescriptor();

	public abstract boolean hasBufferedData();

	/**
	 * Returns whether the close handshake has been completed and the socket is
	 * closed.
	 */
	public abstract boolean isClosed();

	public abstract boolean isClosing();

	public abstract boolean isConnecting();

	/**
	 * Returns true when no further frames may be submitted<br>
	 * This happens before the socket connection is closed.
	 */
	public abstract boolean isFlushAndClose();

	public abstract boolean isOpen();

	public abstract void send(byte[] bytes) throws IllegalArgumentException,
			NotYetConnectedException;

	/**
	 * Send Binary data (plain bytes) to the other end.
	 * 
	 * @throws IllegalArgumentException
	 * @throws NotYetConnectedException
	 */
	public abstract void send(ByteBuffer bytes)
			throws IllegalArgumentException, NotYetConnectedException;

	/**
	 * Send Text data to the other end.
	 * 
	 * @throws IllegalArgumentException
	 * @throws NotYetConnectedException
	 */
	public abstract void send(String text) throws NotYetConnectedException;

	/**
	 * Allows to send continuous/fragmented frames conveniently. <br>
	 * For more into on this frame type see
	 * http://tools.ietf.org/html/rfc6455#section-5.4<br>
	 * 
	 * If the first frame you send is also the last then it is not a fragmented
	 * frame and will received via onMessage instead of onFragmented even though
	 * it was send by this method.
	 * 
	 * @param op
	 *            This is only important for the first frame in the sequence.
	 *            Opcode.TEXT, Opcode.BINARY are allowed.
	 * @param buffer
	 *            The buffer which contains the payload. It may have no bytes
	 *            remaining.
	 * @param fin
	 *            true means the current frame is the last in the sequence.
	 **/
	public abstract void sendFragmentedFrame(Opcode op, ByteBuffer buffer,
			boolean fin);

	public abstract void sendFrame(Framedata framedata);
} 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 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);
	}

	/**
	 * 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;
		}
	}

	@Deprecated
	public WebSocketImpl(WebSocketListener listener, List<Draft> drafts,
			Socket socket) {
		this(listener, drafts);
	}

	@Override
	public void close() {
		close(CloseFrame.NORMAL);
	}

	@Override
	public void close(int code) {
		close(code, "", false);
	}

	@Override
	public void close(int code, String message) {
		close(code, message, false);
	}

	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;
		}
	}

	public void close(InvalidDataException e) {
		close(e.getCloseCode(), e.getMessage(), false);
	}

	public void closeConnection() {
		if (closedremotely == null) {
			throw new IllegalStateException(
					"this method must be used in conjuction with flushAndClose");
		}
		closeConnection(closecode, closemessage, closedremotely);
	}

	protected void closeConnection(int code, boolean remote) {
		closeConnection(code, "", remote);
	}

	@Override
	public void closeConnection(int code, String message) {
		closeConnection(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();
	}

	/**
	 * 
	 */
	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());
	}

	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;
		}
	}

	/**
	 * 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;
	}

	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);
		}
	}

	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;
	}

	@Override
	public Draft getDraft() {
		return draft;
	}

	@Override
	public InetSocketAddress getLocalSocketAddress() {
		return wsl.getLocalSocketAddress(this);
	}

	@Override
	public READYSTATE getReadyState() {
		return readystate;
	}

	@Override
	public InetSocketAddress getRemoteSocketAddress() {
		return wsl.getRemoteSocketAddress(this);
	}

	@Override
	public String getResourceDescriptor() {
		return resourceDescriptor;
	}

	@Override
	public boolean hasBufferedData() {
		return !this.outQueue.isEmpty();
	}

	@Override
	public int hashCode() {
		return super.hashCode();
	}

	@Override
	public boolean isClosed() {
		return readystate == READYSTATE.CLOSED;
	}

	@Override
	public boolean isClosing() {
		return readystate == READYSTATE.CLOSING;
	}

	@Override
	public boolean isConnecting() {
		assert (flushandclosestate ? readystate == READYSTATE.CONNECTING : true);
		return readystate == READYSTATE.CONNECTING; // ifflushandclosestate
	}

	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;
		}
	}

	@Override
	public boolean isFlushAndClose() {
		return flushandclosestate;
	}

	@Override
	public boolean isOpen() {
		assert (readystate == READYSTATE.OPEN ? !flushandclosestate : true);
		return readystate == READYSTATE.OPEN;
	}

	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 void send(byte[] bytes) throws IllegalArgumentException,
			WebsocketNotConnectedException {
		send(ByteBuffer.wrap(bytes));
	}

	/**
	 * 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));
	}

	private void send(Collection<Framedata> frames) {
		if (!isOpen())
			throw new WebsocketNotConnectedException();
		for (Framedata f : frames) {
			sendFrame(f);
		}
	}

	/**
	 * 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));
	}

	@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));
	}

	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));
	}

	@Override
	public String toString() {
		return super.toString(); // its nice to be able to set breakpoints here
	}

	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);
		}
	}

} public abstract class Draft {

	public enum CloseHandshakeType {
		NONE, ONEWAY, TWOWAY
	}

	public enum HandshakeState {
		/** Handshake matched this Draft successfully */
		MATCHED,
		/** Handshake is does not match this Draft */
		NOT_MATCHED
	}

	public static int MAX_FAME_SIZE = 1000 * 1;
	public static int INITIAL_FAMESIZE = 64;

	public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions
			.utf8Bytes("<policy-file-request/>\0");

	public static ByteBuffer readLine(ByteBuffer buf) {
		ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining());
		byte prev = '0';
		byte cur = '0';
		while (buf.hasRemaining()) {
			prev = cur;
			cur = buf.get();
			sbuf.put(cur);
			if (prev == (byte) '\r' && cur == (byte) '\n') {
				sbuf.limit(sbuf.position() - 2);
				sbuf.position(0);
				return sbuf;

			}
		}
		// ensure that there wont be any bytes skipped
		buf.position(buf.position() - sbuf.position());
		return null;
	}

	public static String readStringLine(ByteBuffer buf) {
		ByteBuffer b = readLine(buf);
		return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0,
				b.limit());
	}

	public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf,
			Role role) throws InvalidHandshakeException,
			IncompleteHandshakeException {
		HandshakeBuilder handshake;

		String line = readStringLine(buf);
		if (line == null)
			throw new IncompleteHandshakeException(buf.capacity() + 128);

		String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101
														// Switching the
														// Protocols
		if (firstLineTokens.length != 3) {
			throw new InvalidHandshakeException();
		}

		if (role == Role.CLIENT) {
			// translating/parsing the response from the SERVER
			handshake = new HandshakeImpl1Server();
			ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
			serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1]));
			serverhandshake.setHttpStatusMessage(firstLineTokens[2]);
		} else {
			// translating/parsing the request from the CLIENT
			ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
			clienthandshake.setResourceDescriptor(firstLineTokens[1]);
			handshake = clienthandshake;
		}

		line = readStringLine(buf);
		while (line != null && line.length() > 0) {
			String[] pair = line.split(":", 2);
			if (pair.length != 2)
				throw new InvalidHandshakeException("not an http header");
			handshake.put(pair[0], pair[1].replaceFirst("^ +", ""));
			line = readStringLine(buf);
		}
		if (line == null)
			throw new IncompleteHandshakeException();
		return handshake;
	}

	/**
	 * In some cases the handshake will be parsed different depending on whether
	 */
	protected Role role = null;

	protected Opcode continuousFrameType = null;

	public abstract HandshakeState acceptHandshakeAsClient(
			ClientHandshake request, ServerHandshake response)
			throws InvalidHandshakeException;

	public abstract HandshakeState acceptHandshakeAsServer(
			ClientHandshake handshakedata) throws InvalidHandshakeException;

	protected boolean basicAccept(Handshakedata handshakedata) {
		return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase(
				"websocket")
				&& handshakedata.getFieldValue("Connection")
						.toLowerCase(Locale.ENGLISH).contains("upgrade");
	}

	public int checkAlloc(int bytecount) throws LimitExedeedException,
			InvalidDataException {
		if (bytecount < 0)
			throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
					"Negative count");
		return bytecount;
	}

	public List<Framedata> continuousFrame(Opcode op, ByteBuffer buffer,
			boolean fin) {
		if (op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT) {
			throw new IllegalArgumentException(
					"Only Opcode.BINARY or  Opcode.TEXT are allowed");
		}

		if (continuousFrameType != null) {
			continuousFrameType = Opcode.CONTINUOUS;
		} else {
			continuousFrameType = op;
		}

		FrameBuilder bui = new FramedataImpl1(continuousFrameType);
		try {
			bui.setPayload(buffer);
		} catch (InvalidDataException e) {
			throw new RuntimeException(e); // can only happen when one builds
											// close frames(Opcode.Close)
		}
		bui.setFin(fin);
		if (fin) {
			continuousFrameType = null;
		} else {
			continuousFrameType = op;
		}
		return Collections.singletonList((Framedata) bui);
	}

	/**
	 * Drafts must only be by one websocket at all. To prevent drafts to be used
	 * more than once the Websocket implementation should call this method in
	 * order to create a new usable version of a given draft instance.<br>
	 * The copy can be safely used in conjunction with a new websocket
	 * connection.
	 * */
	public abstract Draft copyInstance();

	public abstract ByteBuffer createBinaryFrame(Framedata framedata); // TODO
																		// Allow
																		// to
																		// send
																		// data
																		// on
																		// the
																		// base
																		// of an
																		// Iterator
																		// or
																		// InputStream

	public abstract List<Framedata> createFrames(ByteBuffer binary, boolean mask);

	public abstract List<Framedata> createFrames(String text, boolean mask);

	public List<ByteBuffer> createHandshake(Handshakedata handshakedata,
			Role ownrole) {
		return createHandshake(handshakedata, ownrole, true);
	}

	public List<ByteBuffer> createHandshake(Handshakedata handshakedata,
			Role ownrole, boolean withcontent) {
		StringBuilder bui = new StringBuilder(100);
		if (handshakedata instanceof ClientHandshake) {
			bui.append("GET ");
			bui.append(((ClientHandshake) handshakedata)
					.getResourceDescriptor());
			bui.append(" HTTP/1.1");
		} else if (handshakedata instanceof ServerHandshake) {
			bui.append("HTTP/1.1 101 "
					+ ((ServerHandshake) handshakedata).getHttpStatusMessage());
		} else {
			throw new RuntimeException("unknow role");
		}
		bui.append("\r\n");
		Iterator<String> it = handshakedata.iterateHttpFields();
		while (it.hasNext()) {
			String fieldname = it.next();
			String fieldvalue = handshakedata.getFieldValue(fieldname);
			bui.append(fieldname);
			bui.append(": ");
			bui.append(fieldvalue);
			bui.append("\r\n");
		}
		bui.append("\r\n");
		byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString());

		byte[] content = withcontent ? handshakedata.getContent() : null;
		ByteBuffer bytebuffer = ByteBuffer.allocate((content == null ? 0
				: content.length) + httpheader.length);
		bytebuffer.put(httpheader);
		if (content != null)
			bytebuffer.put(content);
		bytebuffer.flip();
		return Collections.singletonList(bytebuffer);
	}

	public abstract CloseHandshakeType getCloseHandshakeType();

	public Role getRole() {
		return role;
	}

	public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
			ClientHandshakeBuilder request) throws InvalidHandshakeException;

	public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(
			ClientHandshake request, ServerHandshakeBuilder response)
			throws InvalidHandshakeException;

	public abstract void reset();

	public void setParseMode(Role role) {
		this.role = role;
	}

	public abstract List<Framedata> translateFrame(ByteBuffer buffer)
			throws InvalidDataException;

	public Handshakedata translateHandshake(ByteBuffer buf)
			throws InvalidHandshakeException {
		return translateHandshakeHttp(buf, role);
	}

} public class Draft_17 extends Draft_10 {
	@Override
	public HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata)
			throws InvalidHandshakeException {
		int v = readVersion(handshakedata);
		if (v == 13)
			return HandshakeState.MATCHED;
		return HandshakeState.NOT_MATCHED;
	}

	@Override
	public Draft copyInstance() {
		return new Draft_17();
	}

	@Override
	public ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
			ClientHandshakeBuilder request) {
		super.postProcessHandshakeRequestAsClient(request);
		request.put("Sec-WebSocket-Version", "13");// overwriting the previous
		return request;
	}

} public class InvalidHandshakeException extends InvalidDataException {

	/**
	 * Serializable
	 */
	private static final long serialVersionUID = -1426533877490484964L;

	public InvalidHandshakeException() {
		super(CloseFrame.PROTOCOL_ERROR);
	}

	public InvalidHandshakeException(String arg0) {
		super(CloseFrame.PROTOCOL_ERROR, arg0);
	}

	public InvalidHandshakeException(String arg0, Throwable arg1) {
		super(CloseFrame.PROTOCOL_ERROR, arg0, arg1);
	}

	public InvalidHandshakeException(Throwable arg0) {
		super(CloseFrame.PROTOCOL_ERROR, arg0);
	}

} public interface CloseFrame extends Framedata {
	/**
	 * indicates a normal closure, meaning whatever purpose the connection was
	 * established for has been fulfilled.
	 */
	public static final int NORMAL = 1000;
	/**
	 * 1001 indicates that an endpoint is "going away", such as a server going
	 * down, or a browser having navigated away from a page.
	 */
	public static final int GOING_AWAY = 1001;
	/**
	 * 1002 indicates that an endpoint is terminating the connection due to a
	 * protocol error.
	 */
	public static final int PROTOCOL_ERROR = 1002;
	/**
	 * 1003 indicates that an endpoint is terminating the connection because it
	 * has received a type of data it cannot accept (e.g. an endpoint that
	 * understands only text data MAY send this if it receives a binary
	 * message).
	 */
	public static final int REFUSE = 1003;
	/* 1004: Reserved. The specific meaning might be defined in the future. */
	/**
	 * 1005 is a reserved value and MUST NOT be set as a status code in a Close
	 * control frame by an endpoint. It is designated for use in applications
	 * expecting a status code to indicate that no status code was actually
	 * present.
	 */
	public static final int NOCODE = 1005;
	/**
	 * 1006 is a reserved value and MUST NOT be set as a status code in a Close
	 * control frame by an endpoint. It is designated for use in applications
	 * expecting a status code to indicate that the connection was closed
	 * abnormally, e.g. without sending or receiving a Close control frame.
	 */
	public static final int ABNORMAL_CLOSE = 1006;
	/**
	 * 1007 indicates that an endpoint is terminating the connection because it
	 * has received data within a message that was not consistent with the type
	 * of the message (e.g., non-UTF-8 [RFC3629] data within a text message).
	 */
	public static final int NO_UTF8 = 1007;
	/**
	 * 1008 indicates that an endpoint is terminating the connection because it
	 * has received a message that violates its policy. This is a generic status
	 * code that can be returned when there is no other more suitable status
	 * code (e.g. 1003 or 1009), or if there is a need to hide specific details
	 * about the policy.
	 */
	public static final int POLICY_VALIDATION = 1008;
	/**
	 * 1009 indicates that an endpoint is terminating the connection because it
	 * has received a message which is too big for it to process.
	 */
	public static final int TOOBIG = 1009;
	/**
	 * 1010 indicates that an endpoint (client) is terminating the connection
	 * because it has expected the server to negotiate one or more extension,
	 * but the server didn't return them in the response message of the
	 * WebSocket handshake. The list of extensions which are needed SHOULD
	 * appear in the /reason/ part of the Close frame. Note that this status
	 * code is not used by the server, because it can fail the WebSocket
	 * handshake instead.
	 */
	public static final int EXTENSION = 1010;
	/**
	 * 1011 indicates that a server is terminating the connection because it
	 * encountered an unexpected condition that prevented it from fulfilling the
	 * request.
	 **/
	public static final int UNEXPECTED_CONDITION = 1011;
	/**
	 * 1015 is a reserved value and MUST NOT be set as a status code in a Close
	 * control frame by an endpoint. It is designated for use in applications
	 * expecting a status code to indicate that the connection was closed due to
	 * a failure to perform a TLS handshake (e.g., the server certificate can't
	 * be verified).
	 **/
	public static final int TLS_ERROR = 1015;

	/** The connection had never been established */
	public static final int NEVER_CONNECTED = -1;
	public static final int BUGGYCLOSE = -2;
	public static final int FLASHPOLICY = -3;

	public int getCloseCode() throws InvalidFrameException;

	public String getMessage() throws InvalidDataException;
} public interface Framedata {
	public enum Opcode {
		CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
		// more to come
	}

	public abstract void append(Framedata nextframe)
			throws InvalidFrameException;

	public Opcode getOpcode();

	public ByteBuffer getPayloadData();// TODO the separation of the application
										// data and the extension data is yet to
										// be done

	public boolean getTransfereMasked();

	public boolean isFin();
} public enum Opcode {
	CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
	// more to come
} public class HandshakeImpl1Client extends HandshakedataImpl1 implements
		ClientHandshakeBuilder {
	private String resourceDescriptor = "*";

	public HandshakeImpl1Client() {
	}

	@Override
	public String getResourceDescriptor() {
		return resourceDescriptor;
	}

	@Override
	public void setResourceDescriptor(String resourceDescriptor)
			throws IllegalArgumentException {
		if (resourceDescriptor == null)
			throw new IllegalArgumentException(
					"http resource descriptor must not be null");
		this.resourceDescriptor = resourceDescriptor;
	}
} public interface Handshakedata {
	public byte[] getContent();

	public String getFieldValue(String name);

	public boolean hasFieldValue(String name);

	public Iterator<String> iterateHttpFields();
} public interface ServerHandshake extends Handshakedata {
	public short getHttpStatus();

	public String getHttpStatusMessage();
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.Framedata.Opcode;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
	}

	@Override
	public void onWebsocketMessageFragment(WebSocket conn, Framedata frame) {
		onFragment(frame);
	}

	/**
	 * Calls subclass' implementation of <var>onOpen</var>.
	 */
	@Override
	public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
		connectLatch.countDown();
		onOpen((ServerHandshake) handshake);
	}

	@Override
	public final void onWriteDemand(WebSocket conn) {
		// nothing to do
	}

	@Override
	public void run() {
		try {
			if (socket == null) {
				socket = new Socket(proxy);
			} else if (socket.isClosed()) {
				throw new IOException();
			}
			if (!socket.isBound())
				socket.connect(new InetSocketAddress(uri.getHost(), getPort()),
						connectTimeout);
			istream = socket.getInputStream();
			ostream = socket.getOutputStream();

			sendHandshake();
		} catch ( /*
				 * IOException | SecurityException | UnresolvedAddressException
				 * | InvalidHandshakeException | ClosedByInterruptException |
				 * SocketTimeoutException
				 */Exception e) {
			onWebsocketError(engine, e);
			engine.closeConnection(CloseFrame.NEVER_CONNECTED, e.getMessage());
			return;
		}

		writeThread = new Thread(new WebsocketWriteThread());
		writeThread.start();

		byte[] rawbuffer = new byte[WebSocketImpl.RCVBUF];
		int readBytes;

		try {
			while (!isClosed() && (readBytes = istream.read(rawbuffer)) != -1) {
				engine.decode(ByteBuffer.wrap(rawbuffer, 0, readBytes));
			}
			engine.eot();
		} catch (IOException e) {
			engine.eot();
		} catch (RuntimeException e) {
			// this catch case covers internal errors only and indicates a bug
			// in this websocket implementation
			onError(e);
			engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
		}
		assert (socket.isClosed());
	}

	/**
	 * Sends binary <var> data</var> to the connected webSocket server.
	 * 
	 * @param data
	 *            The byte-Array of data to send to the WebSocket server.
	 */
	@Override
	public void send(byte[] data) throws NotYetConnectedException {
		engine.send(data);
	}

	@Override
	public void send(ByteBuffer bytes) throws IllegalArgumentException,
			NotYetConnectedException {
		engine.send(bytes);
	}

	/**
	 * Sends <var>text</var> to the connected websocket server.
	 * 
	 * @param text
	 *            The string which will be transmitted.
	 */
	@Override
	public void send(String text) throws NotYetConnectedException {
		engine.send(text);
	}

	@Override
	public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
		engine.sendFragmentedFrame(op, buffer, fin);
	}

	@Override
	public void sendFrame(Framedata framedata) {
		engine.sendFrame(framedata);
	}

	private void sendHandshake() throws InvalidHandshakeException {
		String path;
		String part1 = uri.getPath();
		String part2 = uri.getQuery();
		if (part1 == null || part1.length() == 0)
			path = "/";
		else
			path = part1;
		if (part2 != null)
			path += "?" + part2;
		int port = getPort();
		String host = uri.getHost()
				+ (port != WebSocket.DEFAULT_PORT ? ":" + port : "");

		HandshakeImpl1Client handshake = new HandshakeImpl1Client();