content: /*
   *   Copyright 2009-2010 Joubin Houshyar
   * 
   *   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 org.jredis.ri.alphazero.protocol;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.net.SocketException;
  
  import org.jredis.ClientRuntimeException;
  import org.jredis.NotSupportedException;
  import org.jredis.ProviderException;
  import org.jredis.protocol.Command;
  import org.jredis.protocol.Protocol;
  import org.jredis.protocol.Request;
  import org.jredis.protocol.Response;
  import org.jredis.protocol.ResponseStatus;
  import org.jredis.ri.alphazero.support.Assert;
  import org.jredis.ri.alphazero.support.Convert;
  import org.jredis.ri.alphazero.support.Log;
  
  
  /**
   * [TODO: document me!]
   *
   * @author  Joubin (alphazero@sensesay.net)
   * @version alpha.0, Apr 10, 2009
   * @since   alpha.0
   * 
   */
  public abstract class ProtocolBase implements Protocol {
  
  	// ------------------------------------------------------------------------
  	// Protocol Revision specific 
  	// ------------------------------------------------------------------------
  
  	public static final byte[] 	CRLF = {(byte) 13, (byte)10};
  	public static final byte[] 	SPACE = {(byte) 32};
  	public static final int 	CRLF_LEN = CRLF.length;
  	public static final int 	DELIMETER_LEN = SPACE.length;
  	public static final byte	ERR_BYTE 	= (byte) 45; // -
  	public static final byte	OK_BYTE 	= (byte) 43; // +
  	public static final byte	COUNT_BYTE 	= (byte) 42; // *
  	public static final byte	SIZE_BYTE 	= (byte) 36; // $
  	public static final byte	NUM_BYTE 	= (byte) 58; // :
  	public static final byte	ASCII_ZERO	= (byte) 48; // 0
  	
  	// ------------------------------------------------------------------------
  	// Protocol Revision specific consts
  	// ------------------------------------------------------------------------
  	protected ProtocolBase () {}
  	
  	// ------------------------------------------------------------------------
  	// Interface: Protocol
  	// ------------------------------------------------------------------------
  	
  	/* TODO: lets just forget about this multi-version nonsense. */  
  	@Override
  	public boolean isCompatibleWithVersion(String version) {
  		return version.equals("0.09");
  	}
  	
  	public byte[] createRequestBuffer(Command cmd, byte[]...args) throws ProviderException, IllegalArgumentException {
  		class Buffer {
  			byte[] b = null;
  			private int off = 0;
  			Buffer(int size){
  				b = new byte[size];
  			}
  			void write (byte[] d){
  				final int dlen = d.length;
  				System.arraycopy(d, 0, b, off, dlen);
  				off+=dlen;
  			}
  			void write (byte d){
  				b[off] = d;
  				off++;
  			}
  			byte[] getBytes() {
  				return b;
  			}
  		}
  		Buffer buffer = null;
  		byte[] cmdLenBytes = Convert.toBytes(cmd.bytes.length);
  		byte[] lineCntBytes = Convert.toBytes(args.length+1);
  
  		// calculate the buffer size
  		int bsize = 1 + lineCntBytes.length + CRLF_LEN + 1 + cmdLenBytes.length + CRLF_LEN + cmd.bytes.length + CRLF_LEN;
  		for(int i=0;i<args.length; i++){
  			byte[] argLenBytes = Convert.toBytes(Assert.notNull(args[i], i, ProviderException.class).length);
  			int _bsize = 1 + argLenBytes.length + CRLF_LEN + args[i].length + CRLF_LEN;
  			bsize += _bsize;
  		}
  		
  		buffer = new Buffer(bsize);
          buffer.write(COUNT_BYTE);  		// 1
          buffer.write(lineCntBytes);		// lineCntBytes.length()
          buffer.write(CRLF);				// CRLF.lengt
          buffer.write(SIZE_BYTE);		// 1
          buffer.write(cmdLenBytes);		// length
          buffer.write(CRLF);				// CRLF_LEN
          buffer.write(cmd.bytes);		//
          buffer.write(CRLF);
          
  		switch (cmd.requestType) {
  
  		case NO_ARG:
  		    break;
  		    
  		// TODO: check w/ antirez if in fact nulls are now generally accepted
  		// that is the only diff here.
  		case BULK_SET:
  			String errmsg = "Only MSET, MSETNX, LINSERT bulk commands are supported";
  			Assert.isTrue(cmd == Command.MSET || cmd == Command.MSETNX || cmd == Command.LINSERT, errmsg, NotSupportedException.class);
  
  			// THIS IS CLEARLY BROKEN ... SO MUCH FOR COMPREHSIVE TESTS ...
  			buffer.write(COUNT_BYTE);
  			buffer.write(lineCntBytes);
  			buffer.write(CRLF);
  			buffer.write(SIZE_BYTE);
  			buffer.write(cmdLenBytes);
  			buffer.write(CRLF);
  			buffer.write(cmd.bytes);
  			buffer.write(CRLF);
  			
  			for(int s=0; s<args.length; s++){
  				buffer.write(SIZE_BYTE);
  				if (args[s] != null) {
    					buffer.write(Convert.toBytes(args[s].length));
    					buffer.write(CRLF);
    					buffer.write(args[s]);
    					buffer.write(CRLF);
    				} else {
  					buffer.write(ASCII_ZERO);
  					buffer.write(CRLF);
  					buffer.write(CRLF);
    				}
  			}
  			break;
  		
  		default:
  			for(int i=0;i<args.length; i++){
  				buffer.write(SIZE_BYTE);
  				buffer.write(Convert.toBytes(Assert.notNull(args[i], i, ProviderException.class).length));
  				buffer.write(CRLF);
  				buffer.write(args[i]);
  				buffer.write(CRLF);
  			}
  			break;
  		
  		}
  		return buffer.getBytes();
  	}
  
  	/* (non-Javadoc)
  	 * @see org.jredis.connector.Protocol#createRequest(org.jredis.Command, byte[][])
  	 */
  	@Override
  	public Request createRequest(Command cmd, byte[]... args) throws ProviderException, IllegalArgumentException {
  		
  		ByteArrayOutputStream buffer = createRequestBufffer (cmd);
  
  		try {
  			byte[] cmdLenBytes = Convert.toBytes(cmd.bytes.length);
  			byte[] lineCntBytes = Convert.toBytes(args.length+1);
  
              buffer.write(COUNT_BYTE);
              buffer.write(lineCntBytes);
              buffer.write(CRLF);
              buffer.write(SIZE_BYTE);
              buffer.write(cmdLenBytes);
              buffer.write(CRLF);
              buffer.write(cmd.bytes);
              buffer.write(CRLF);
              
  			switch (cmd.requestType) {
  
  			case NO_ARG:
  			    break;
  			    
  			// TODO: check w/ antirez if in fact nulls are now generally accepted
  			// that is the only diff here.
  			case BULK_SET:
  				String errmsg = "Only MSET, MSETNX, LINSERT bulk commands are supported";
  				Assert.isTrue(cmd == Command.MSET || cmd == Command.MSETNX || cmd == Command.LINSERT, errmsg, NotSupportedException.class);
  
  				// THIS IS CLEARLY BROKEN ... SO MUCH FOR COMPREHSIVE TESTS ...
  				buffer.write(COUNT_BYTE);
  				buffer.write(lineCntBytes);
  				buffer.write(CRLF);
  				buffer.write(SIZE_BYTE);
  				buffer.write(cmdLenBytes);
  				buffer.write(CRLF);
  				buffer.write(cmd.bytes);
  				buffer.write(CRLF);
  				
  				for(int s=0; s<args.length; s++){
  					buffer.write(SIZE_BYTE);
  					if (args[s] != null) {
  	  					buffer.write(Convert.toBytes(args[s].length));
  	  					buffer.write(CRLF);
  	  					buffer.write(args[s]);
  	  					buffer.write(CRLF);
  	  				} else {
  						buffer.write(ASCII_ZERO);
  						buffer.write(CRLF);
  						buffer.write(CRLF);
  	  				}
  				}
  				break;
  			
  			default:
  				for(int i=0;i<args.length; i++){
  					buffer.write(SIZE_BYTE);
  					buffer.write(Convert.toBytes(Assert.notNull(args[i], i, ProviderException.class).length));
  					buffer.write(CRLF);
  					buffer.write(args[i]);
  					buffer.write(CRLF);
  				}
  				break;
  			
  			}
  		}
  		catch (IOException e) {
  			throw new ProviderException("Problem writing to the buffer" + e.getLocalizedMessage(), e);
  		}
  		return createRequest(buffer);
  	}
  	
  	/* (non-Javadoc)
  	 * @see org.jredis.connector.Protocol#createResponse(org.jredis.Command)
  	 */
  	@Override
  	public Response createResponse(Command cmd) throws ProviderException, ClientRuntimeException {
  
  		Response response = null;
  		switch (cmd.responseType){
  		case BOOLEAN:
  			response = createBooleanResponse(cmd);
  			break;
  		case BULK:
  			response = createBulkResponse (cmd);
  			break;
  		case MULTI_BULK:
  			response = createMultiBulkResponse (cmd);
  			break;
  		case NUMBER:
  			response = createNumberResponse (cmd);
  			break;
  		case STATUS:
  			response = createStatusResponse (cmd);
  			break;
  		case STRING:
  			response = createStringResponse (cmd);
  			break;
  		case VIRTUAL:
  			response = new VirtualResponse(ResponseStatus.STATUS_CIAO);
  			break;
  		case NOP:
  			response = new VirtualResponse(ResponseStatus.STATUS_OK); // TODO: needs more thinking ..
  			break;
  		case QUEUED:
  		case RESULT_SET:
  			throw new NotSupportedException(String.format("ResponseType %s not yet supported", cmd.requestType.name()));
  		
  		}
  
  		return response;
  	
  	}
  
  
  	// ------------------------------------------------------------------------
  	// Extension Points
  	// ------------------------------------------------------------------------
  	
  	protected abstract ByteArrayOutputStream createRequestBufffer(Command cmd);
  	protected abstract Request createRequest (ByteArrayOutputStream buffer);
  	protected abstract Response createMultiBulkResponse(Command cmd) ;
  	protected abstract Response createBulkResponse(Command cmd) ;
  	protected abstract Response createNumberResponse(Command cmd /*, boolean bigNum*/) ;
  	protected abstract Response createBooleanResponse(Command cmd) ;
  	protected abstract Response createStringResponse(Command cmd) ;
  	protected abstract Response createStatusResponse(Command cmd);
  
  	
  	// ========================================================================
  	// Inner Types
  	// ========================================================================
  	
  	/**
  	 * SimpleRequest implements the required {@link Request#read(InputStream)}
  	 * using a (likely) shared data buffer.  It is not thread safe and can only
  	 * be used by connections that serialized request processing through a single
  	 * requesting thread.
  	 * <p>
  	 * Specifically, if an instance of this class is obtained and not immediately
  	 * used (by calling read) before the user has obtained another instance of the
  	 * same, data corruption is guaranteed.
  	 *
  	 * @author  Joubin Houshyar (alphazero@sensesay.net)
  	 * @version alpha.0, 04/02/09
  	 * @since   alpha.0
  	 * 
  	 */
  	public static class StreamBufferRequest implements Request {
  
  		/**  */
  		ByteArrayOutputStream buffer;
  		/**
  		 * @param buffer
  		 */
  		public StreamBufferRequest (ByteArrayOutputStream	buffer) {
  			this.buffer = buffer;
  		}
  		public void reset (ByteArrayOutputStream	buffer) {
  			this.buffer = buffer;
  		}
  		/* (non-Javadoc)
  		 * @see com.alphazero.jredis.connector.Message#read(java.io.InputStream)
  		 */
  		@Override
  		public void read(InputStream in) throws ClientRuntimeException, ProviderException {
  			throw new ProviderException("Request.read is not supported by this class! [Apr 2, 2009]");
  		}
  
  		/**
  		 * Writes the entire content of the message to the output stream and flushes it.
  		 * 
  		 * @param out the stream to write the Request message to.
  		 */
  		@Override
  		public void write(OutputStream out) throws ClientRuntimeException, ProviderException {
  			try {
  				// you would expect these to throw exceptions if the socket has been reset
  				// but they don't.  
  				buffer.writeTo(out);
  				out.flush();
  			}
  			catch (SocketException e){
  				Log.error("StreamBufferRequest.write(): SocketException on write: " + e.getLocalizedMessage());
  				throw new ClientRuntimeException ("socket exception", e);
  			}
  			catch (IOException e) { 
  				Log.error("StreamBufferRequest.write(): IOException on write: " + e.getLocalizedMessage());
  				throw new ClientRuntimeException ("stream io exception", e);
  			}
  		}
  	}
  }
  