/** *Copyright [2010-2011] [dennis zhuang(killme2008@gmail.com)] *Licensed 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 com.google.code.hs4j.command.text; import java.io.UnsupportedEncodingException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import com.google.code.hs4j.Command; import com.google.code.hs4j.network.buffer.IoBuffer; import com.google.code.hs4j.network.hs.HandlerSocketSession; import com.google.code.hs4j.network.util.ByteBufferMatcher; import com.google.code.hs4j.network.util.ShiftAndByteBufferMatcher; public abstract class AbstractCommand implements Command { public static final byte TOKEN_SEPARATOR = 0x09; public static final byte COMMAND_TERMINATE = 0x0a; public static final String OPERATOR_OPEN_INDEX = "P"; public static final String OPERATOR_INSERT = "+"; public static final String OPERATOR_UPDATE = "U"; public static final String OPERATOR_DELETE = "D"; public static final String OPERATOR_INCREMENT = "+"; public static final String OPERATOR_DECREMENT = "-"; protected static IoBuffer TERMINATER = IoBuffer.allocate(1); protected static IoBuffer SEPERATOR = IoBuffer.allocate(1); protected static ByteBufferMatcher TERMIATER_MATCHER; protected static ByteBufferMatcher SEPERATOR_MATCHER; static { TERMINATER.put(COMMAND_TERMINATE); TERMINATER.flip(); SEPERATOR.put(TOKEN_SEPARATOR); SEPERATOR.flip(); TERMIATER_MATCHER = new ShiftAndByteBufferMatcher(TERMINATER); SEPERATOR_MATCHER = new ShiftAndByteBufferMatcher(SEPERATOR); } public static final String DEFAULT_ENCODING = "UTF-8"; protected String encoding = DEFAULT_ENCODING; private final CountDownLatch latch; protected IoBuffer buffer; protected Object result; private Future<Boolean> writeFuture; private String exceptionMsg; private boolean ignoreError = false; public AbstractCommand() { super(); this.latch = new CountDownLatch(1); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return this.latch.await(timeout, unit); } public void countDown() { this.latch.countDown(); } public boolean getIgnoreError() { return ignoreError; } public void setIgnoreError(final boolean ignoreError) { this.ignoreError = ignoreError; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } public IoBuffer getIoBuffer() { return this.buffer; } public String getExceptionMessage() { return this.exceptionMsg; } public void setExceptionMessage(String t) { this.exceptionMsg = t; } public Object getResult() { return this.result; } public Future<Boolean> getWriteFuture() { return this.writeFuture; } public void setWriteFuture(Future<Boolean> future) { this.writeFuture = future; } protected void writeToken(IoBuffer buf, String token) { if (token == null) { buf.put((byte) 0x00); } else { byte[] bytes = decodeString(token); writeToken(buf, bytes); } } protected void writeToken(IoBuffer buf, byte[] token) { if (token == null) { buf.put((byte) 0x00); } else { for (byte b : token) { if (b >= 0 && b <= 0x0f) { buf.put((byte) 0x01); buf.put((byte) (b | 0x40)); } else { buf.put(b); } } } } protected static String join(String[] values) { return join(values, ","); } protected static String join(String[] values, String split) { if (values == null || values.length == 0) { return ""; } StringBuilder sb = new StringBuilder(); boolean wasFirst = true; for (String value : values) { if (wasFirst) { sb.append(value); wasFirst = false; } else { sb.append(split).append(value); } } return sb.toString(); } protected int length(byte[][] values) { if (values == null || values.length == 0) { return 0; } int result = 0; for (byte[] value : values) { if (value != null) { result += value.length; } } return result; } private ParseState currentState; private int responseStatus; private int numColumns; private final StringBuilder numColumnsAppender = new StringBuilder(5); private byte[] body; public int getResponseStatus() { return this.responseStatus; } public int getNumColumns() { return this.numColumns; } static enum ParseState { STATUS, NUMCOLUMNS, BODY, DONE } protected static void skipSeperator(IoBuffer buffer) { byte b = buffer.get(); if (b != TOKEN_SEPARATOR) { throw new RuntimeException( "Decode error,expect sepeartor 0x09,but was " + b); } } public boolean decode(HandlerSocketSession session, IoBuffer buffer) { if (this.currentState == null) { this.currentState = ParseState.STATUS; } LABEL: while (true) { switch (this.currentState) { case STATUS: if (buffer.remaining() < 2) { return false; } this.responseStatus = buffer.get() - 0x30; skipSeperator(buffer); this.currentState = ParseState.NUMCOLUMNS; continue; case NUMCOLUMNS: if (!buffer.hasRemaining()) { return false; } int remaining = buffer.remaining(); for (int i = 0; i < remaining; i++) { byte b = buffer.get(); if (b == COMMAND_TERMINATE) { this.numColumns = Integer .parseInt(this.numColumnsAppender.toString()); this.currentState = ParseState.DONE; continue LABEL; } else if (b == TOKEN_SEPARATOR) { this.numColumns = Integer .parseInt(this.numColumnsAppender.toString()); this.currentState = ParseState.BODY; continue LABEL; } else { this.numColumnsAppender.append(b - 0x30); } } return false; case BODY: if (!buffer.hasRemaining()) { return false; } int index = TERMIATER_MATCHER.matchFirst(buffer); if (index > 0) { if (this.responseStatus == 0 || ignoreError) { this.copyDataFromBufferToBody(buffer, index - buffer.position() + 1); this.decodeBody(session, this.body, index); } else { this.copyDataFromBufferToBody(buffer, index - buffer.position()); // skip terminator buffer.position(buffer.position() + 1); this.setExceptionMessage("Error message from server:" + this.encodingString(this.body)); } this.currentState = ParseState.DONE; continue; } else { if (buffer.hasRemaining()) { this.copyDataFromBufferToBody(buffer, buffer .remaining()); } return false; } case DONE: this.onDone(); this.countDown(); return true; } } } private void copyDataFromBufferToBody(IoBuffer buffer, int length) { if (this.body == null) { this.body = new byte[length]; buffer.get(this.body); } else { int oldLen = this.body.length; byte[] newBody = new byte[oldLen + length]; // copy body to new body System.arraycopy(this.body, 0, newBody, 0, oldLen); this.body = newBody; buffer.get(this.body, oldLen, length); } } protected void onDone() { } public String encodingString(byte[] data) { try { return new String(data, 0, data.length-1, this.encoding); // skip new line } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unsupported encoding:" + this.encoding, e); } } public byte[] decodeString(String s) { try { return s.getBytes(this.encoding); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unsupported encoding:" + this.encoding, e); } } protected void decodeBody(HandlerSocketSession session, byte[] body, int index) { result = this.encodingString(body); } CountDownLatch getLatch() { return this.latch; } ParseState getCurrentState() { return this.currentState; } protected void writeTokenSeparator(IoBuffer buf) { buf.put(TOKEN_SEPARATOR); } protected void writeCommandTerminate(IoBuffer buf) { buf.put(COMMAND_TERMINATE); } }