content: /*
   * Copyright (c) 2001-2006 Caucho Technology, Inc.  All rights reserved.
   *
   * The Apache Software License, Version 1.1
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Caucho Technology (http://www.caucho.com/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
   *    endorse or promote products derived from this software without prior
   *    written permission. For written permission, please contact
   *    info@caucho.com.
   *
   * 5. Products derived from this software may not be called "Resin"
   *    nor may "Resin" appear in their names without prior written
   *    permission of Caucho Technology.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
   * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
   * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @author Scott Ferguson
   */
  
  package com.caucho.hessian.micro;
  
  import java.io.*;
  import java.util.*;
  
  /**
   * Output stream for Hessian requests, compatible with microedition Java. It
   * only uses classes and types available to J2ME. In particular, it does not
   * have any support for the &lt;double> type.
   * <p>
   * MicroHessianOutput does not depend on any classes other than in J2ME, so it
   * can be extracted independently into a smaller package.
   * <p>
   * MicroHessianOutput is unbuffered, so any client needs to provide its own
   * buffering.
   * 
   * <pre>
   * OutputStream os = ...; // from http connection
   * MicroHessianOutput out = new MicroHessianOutput(os);
   * String value;
   * out.startCall(&quot;hello&quot;);  // start hello call
   * out.writeString(&quot;arg1&quot;); // write a string argument
   * out.completeCall();      // complete the call
   * </pre>
   */
  public class MicroHessianOutput {
      protected OutputStream os;
      
      /** Max Chunk size = 64K. */
      public final static int MAX_CHUNK = 65535;
      
      /** The mask to be used to get the lower byte in an int */
      private final static int LOW_BYTE_MASK = 0xff;
  
      /**
       * Creates a new Hessian output stream, initialized with an underlying
       * output stream.
       * 
       * @param os the underlying output stream.
       */
      public MicroHessianOutput(OutputStream os) {
          init(os);
      }
  
      /**
       * Creates an uninitialized Hessian output stream.
       */
      public MicroHessianOutput() {
      }
  
      public void init(OutputStream os) {
          this.os = os;
      }
  
      /**
       * Writes the method call: <code><pre>
       * c major minor
       * m b16 b8 method-namek
       * </pre></code>
       * 
       * @param method the method name to call.
       */
      public void startCall(String method) throws IOException {
          os.write('c');
          os.write(0);
          os.write(1);
  
          os.write('m');
          int len = method.length();
          os.write(len >> 8);
          os.write(len);
          printString(method, 0, len);
      }
  
      /**
       * Writes the method call: <code><pre>
       * z
       * </pre></code>
       */
      public void completeCall() throws IOException {
          os.write('z');
      }
  
      /**
       * Writes a boolean value to the stream. The boolean will be written with
       * the following syntax: <code><pre>
       * T
       * F
       * </pre></code>
       * 
       * @param value the boolean value to write.
       */
      public void writeBoolean(boolean value) throws IOException {
          if (value)
              os.write('T');
          else
              os.write('F');
      }
  
      /**
       * Writes an integer value to the stream. The integer will be written with
       * the following syntax: <code><pre>
       * I b32 b24 b16 b8
       * </pre></code>
       * 
       * @param value the integer value to write.
       */
      public void writeInt(int value) throws IOException {
          os.write('I');
          os.write(value >> 24);
          os.write(value >> 16);
          os.write(value >> 8);
          os.write(value);
      }
  
      /**
       * Writes a long value to the stream. The long will be written with the
       * following syntax: <code><pre>
       * L b64 b56 b48 b40 b32 b24 b16 b8
       * </pre></code>
       * 
       * @param value the long value to write.
       */
      public void writeLong(long value) throws IOException {
          os.write('L');
          os.write((byte)(value >> 56));
          os.write((byte)(value >> 48));
          os.write((byte)(value >> 40));
          os.write((byte)(value >> 32));
          os.write((byte)(value >> 24));
          os.write((byte)(value >> 16));
          os.write((byte)(value >> 8));
          os.write((byte)(value));
      }
  
      /**
       * Writes a date to the stream. <code><pre>
       * T  b64 b56 b48 b40 b32 b24 b16 b8
       * </pre></code>
       * 
       * @param time the date in milliseconds from the epoch in UTC
       */
      public void writeUTCDate(long time) throws IOException {
          os.write('d');
          os.write((byte)(time >> 56));
          os.write((byte)(time >> 48));
          os.write((byte)(time >> 40));
          os.write((byte)(time >> 32));
          os.write((byte)(time >> 24));
          os.write((byte)(time >> 16));
          os.write((byte)(time >> 8));
          os.write((byte)(time));
      }
  
      /**
       * Writes a null value to the stream. The null will be written with the
       * following syntax <code><pre>
       * N
       * </pre></code>
       * 
       * @param value the string value to write.
       */
      public void writeNull() throws IOException {
          os.write('N');
      }
  
      /**
       * Writes a string value to the stream using UTF-8 encoding. The string will
       * be written with the following syntax: <code><pre>
       * s b16 b8 string-value S b16 b8 string-value
       * </pre></code> If the value is null, it will be written as <code><pre>
       * N
       * </pre></code>
       *
       * @param value the string value to write.
       * @throws  IOException     if the stream fails.
       */
      public void writeString(final String value) throws IOException {
          if (value == null) {
              os.write('N');
              return;
          }
          final int length = value.length();
          final int numChunks = length / MAX_CHUNK;
          int i = 0;
          int offset = 0;
          for ( ; i < numChunks; i++) {
              printStringLen(MAX_CHUNK, false);
              printString(value, offset, MAX_CHUNK);
              offset += MAX_CHUNK;
          }
          printStringLen(length % MAX_CHUNK, true);
          printString(value, offset, (length % MAX_CHUNK));
      }
      
      /**
       * Prints the string length plus the 'S'/'s' prefix.
       *
       * @param   length      the length of the string.
       * @param   isFinalChunk  true for 'S', false for 's'.
       * @throws  IOException   if the stream fails.
       */
      protected void printStringLen(final int length, final boolean isFinalChunk) throws IOException {
          os.write(isFinalChunk ? 'S' : 's');
          os.write(length >> 8 & LOW_BYTE_MASK);
          os.write(length & LOW_BYTE_MASK);
      }
  
      /**
       * Writes a byte array to the stream. The array will be written with the
       * following syntax: <code><pre>
       * B b16 b18 bytes
       * </pre></code> If the value is null, it will be written as <code><pre>
       * N
       * </pre></code>
       * 
       * @param value the string value to write.
       */
      public void writeBytes(byte[] buffer) throws IOException {
          if (buffer == null)
              os.write('N');
          else
              writeBytes(buffer, 0, buffer.length);
      }
  
      /**
       * Writes a byte array to the stream. The array will be written with the
       * following syntax: <code><pre>
       * b b16 b8 bytes B b16 b8 bytes
       * </pre></code> If the value is null, it will be written as <code><pre>
       * N
       * </pre></code>
       *
       * @param buffer the string value to write.
       * @param   offset      the first byte of the byte array to write.
       * @param   length      the number of bytes of the byte array to write.
       * @throws  IOException if the stream fails.
       */
      public void writeBytes(final byte[] buffer, final int offset, final int length) throws IOException {
          if (buffer == null) {
              os.write('N');
              return;
          }
          
          int i = 0;
          final int numChunks = length / MAX_CHUNK;
          int chunkOffset = 0;
          for ( ; i < numChunks; i++) {
              printBytesLen(MAX_CHUNK, false);
              os.write(buffer, offset + chunkOffset, MAX_CHUNK);
              chunkOffset += MAX_CHUNK;
          }
          printBytesLen(length % MAX_CHUNK, true);
          os.write(buffer, offset + chunkOffset, (length % MAX_CHUNK));
  
      }
      
      /**
       * Prints the byte array length plus the 'B'/'b' prefix.
       *
       * @param   length      the length of the byte array.
       * @param   isFinalChunk           true for 'B', false for 'b'.
       * @throws  IOException     if the stream fails.
       */
      protected void printBytesLen(final int length, final boolean isFinalChunk) throws IOException {
          this.os.write(isFinalChunk ? 'B' : 'b');
          this.os.write(length >> 8 & LOW_BYTE_MASK);
          this.os.write(length & LOW_BYTE_MASK);
      }
  
      /**
       * Writes a reference. <code><pre>
       * R b32 b24 b16 b8
       * </pre></code>
       * 
       * @param value the integer value to write.
       */
      public void writeRef(int value) throws IOException {
          os.write('R');
          os.write(value >> 24);
          os.write(value >> 16);
          os.write(value >> 8);
          os.write(value);
      }
  
      /**
       * Writes a generic object to the output stream.
       */
      public void writeObject(Object object) throws IOException {
          if (object == null)
              writeNull();
          else if (object instanceof String)
              writeString((String)object);
          else if (object instanceof Boolean)
              writeBoolean(((Boolean)object).booleanValue());
          else if (object instanceof Integer)
              writeInt(((Number)object).intValue());
          else if (object instanceof Long)
              writeLong(((Number)object).longValue());
          else if (object instanceof Date)
              writeUTCDate(((Date)object).getTime());
          else if (object instanceof byte[]) {
              byte[] data = (byte[])object;
              writeBytes(data, 0, data.length);
          } else if (object instanceof Vector<?>) {
              Vector<?> vector = (Vector<?>)object;
  
              int size = vector.size();
              writeListBegin(size, null);
              for (int i = 0; i < size; i++)
                  writeObject(vector.get(i));
  
              writeListEnd();
          } else if (object instanceof Hashtable<?,?>) {
              @SuppressWarnings("unchecked")
              Hashtable<Object, Object> hashtable = (Hashtable<Object, Object>)object;
  
              writeMapBegin(null);
              Enumeration<Object> e = hashtable.keys();
              while (e.hasMoreElements()) {
                  Object key = e.nextElement();
                  Object value = hashtable.get(key);
  
                  writeObject(key);
                  writeObject(value);
              }
              writeMapEnd();
          } else
              writeCustomObject(object);
      }
  
      /**
       * Applications which override this can do custom serialization.
       * 
       * @param object the object to write.
       */
      public void writeCustomObject(Object object) throws IOException {
          throw new IOException("unexpected object: " + object);
      }
  
      /**
       * Writes the list header to the stream. List writers will call
       * <code>writeListBegin</code> followed by the list contents and then call
       * <code>writeListEnd</code>. <code><pre>
       * &lt;list>
       *   &lt;type>java.util.ArrayList&lt;/type>
       *   &lt;length>3&lt;/length>
       *   &lt;int>1&lt;/int>
       *   &lt;int>2&lt;/int>
       *   &lt;int>3&lt;/int>
       * &lt;/list>
       * </pre></code>
       */
      public void writeListBegin(int length, String type) throws IOException {
          os.write('V');
          os.write('t');
          printLenString(type);
  
          os.write('l');
          os.write(length >> 24);
          os.write(length >> 16);
          os.write(length >> 8);
          os.write(length);
      }
  
      /**
       * Writes the tail of the list to the stream.
       */
      public void writeListEnd() throws IOException {
          os.write('z');
      }
  
      /**
       * Writes the map header to the stream. Map writers will call
       * <code>writeMapBegin</code> followed by the map contents and then call
       * <code>writeMapEnd</code>. <code><pre>
       * Mt b16 b8 type (<key> <value>)z
       * </pre></code>
       */
      public void writeMapBegin(String type) throws IOException {
          os.write('M');
          os.write('t');
          printLenString(type);
      }
  
      /**
       * Writes the tail of the map to the stream.
       */
      public void writeMapEnd() throws IOException {
          os.write('z');
      }
  
      /**
       * Writes a remote object reference to the stream. The type is the type of
       * the remote interface. <code><pre>
       * 'r' 't' b16 b8 type url
       * </pre></code>
       */
      public void writeRemote(String type, String url) throws IOException {
          os.write('r');
          os.write('t');
          printLenString(type);
          os.write('S');
          printLenString(url);
      }
  
      /**
       * Prints a string to the stream, encoded as UTF-8 with preceeding length
       * 
       * @param v the string to print.
       */
      public void printLenString(String v) throws IOException {
          if (v == null) {
              os.write(0);
              os.write(0);
          } else {
              int len = v.length();
              os.write(len >> 8);
              os.write(len);
  
              printString(v, 0, len);
          }
      }
  
      /**
       * Prints a string to the stream, encoded as UTF-8
       * 
       * @param v the string to print.
       */
      public void printString(String v) throws IOException {
          printString(v, 0, v.length());
      }
  
      /**
       * Prints a string to the stream, encoded as UTF-8
       * 
       * @param v the string to print.
       */
      public void printString(String v, int offset, int length) throws IOException {
          for (int i = 0; i < length; i++) {
              char ch = v.charAt(i + offset);
  
              if (ch < 0x80)
                  os.write(ch);
              else if (ch < 0x800) {
                  os.write(0xc0 + ((ch >> 6) & 0x1f));
                  os.write(0x80 + (ch & 0x3f));
              } else if (ch >= 0xd800 && ch <= 0xdf00) {
                  final char[] chars = new char[2];
                  chars[0] = ch;
                  chars[1] = v.charAt(i + offset + 1);
                  os.write(String.valueOf(chars).getBytes("utf-8"));
                  i++;
              } else {
                  os.write(0xe0 + ((ch >> 12) & 0xf));
                  os.write(0x80 + ((ch >> 6) & 0x3f));
                  os.write(0x80 + (ch & 0x3f));
              }
          }
      }
  }
  