You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2009/11/26 07:35:46 UTC

svn commit: r884410 [2/4] - /tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,49 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.File;
+import java.io.IOException;
+
+
+/**
+ * Catalina uses JNDI to abstract filesystem - this is both heavy and
+ * a bit complex. 
+ * 
+ * This is also a bit complex - but hopefully we can implement it as 
+ * non-blocking and without much copy.
+ * 
+ */
+public class FileConnectorJavaIo extends FileConnector {
+    File base;
+    
+    public FileConnectorJavaIo(File file) {
+        this.base = file;
+    }
+
+    @Override
+    public boolean isDirectory(String path) {
+        File file = new File(base, path);
+        return file.isDirectory();
+    }
+
+    @Override
+    public boolean isFile(String path) {
+        File file = new File(base, path);
+        return file.exists() && !file.isDirectory();
+    }
+
+    @Override
+    public void acceptor(ConnectedCallback sc, 
+            CharSequence port, 
+            Object extra) throws IOException {
+        // TODO: unix domain socket impl.
+        // Maybe: detect new files in the filesystem ? 
+    }
+
+    @Override
+    public void connect(String host, int port, ConnectedCallback sc)
+            throws IOException {
+    }
+
+}
\ No newline at end of file

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,249 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.tomcat.lite.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Tables useful when converting byte arrays to and from strings of hexadecimal
+ * digits.
+ * Code from Ajp11, from Apache's JServ.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public final class Hex {
+
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     *  Table for HEX to DEC byte translation.
+     */
+    public static final int[] DEC = {
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        00, 01, 02, 03, 04, 05, 06, 07,  8,  9, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    };
+
+
+    /**
+     * Table for DEC to HEX byte translation.
+     */
+    public static final byte[] HEX = 
+    { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', 
+      (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', 
+      (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
+
+
+    // --------------------------------------------------------- Static Methods
+
+
+    /**
+     * Convert a String of hexadecimal digits into the corresponding
+     * byte array by encoding each two hexadecimal digits as a byte.
+     *
+     * @param digits Hexadecimal digits representation
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is found, or the input string contains an odd number of hexadecimal
+     *  digits
+     */
+    public static byte[] convert(String digits) {
+
+	ByteArrayOutputStream baos = new ByteArrayOutputStream();
+	for (int i = 0; i < digits.length(); i += 2) {
+	    char c1 = digits.charAt(i);
+	    if ((i+1) >= digits.length())
+	        throw new IllegalArgumentException
+		        ("hexUtil.odd");
+	    char c2 = digits.charAt(i + 1);
+	    byte b = 0;
+	    if ((c1 >= '0') && (c1 <= '9'))
+		b += ((c1 - '0') * 16);
+	    else if ((c1 >= 'a') && (c1 <= 'f'))
+		b += ((c1 - 'a' + 10) * 16);
+	    else if ((c1 >= 'A') && (c1 <= 'F'))
+		b += ((c1 - 'A' + 10) * 16);
+	    else
+		throw new IllegalArgumentException
+		    ("hexUtil.bad");
+	    if ((c2 >= '0') && (c2 <= '9'))
+		b += (c2 - '0');
+	    else if ((c2 >= 'a') && (c2 <= 'f'))
+		b += (c2 - 'a' + 10);
+	    else if ((c2 >= 'A') && (c2 <= 'F'))
+		b += (c2 - 'A' + 10);
+	    else
+		throw new IllegalArgumentException
+		    ("hexUtil.bad");
+	    baos.write(b);
+	}
+	return (baos.toByteArray());
+
+    }
+
+
+    /**
+     * Convert a byte array into a printable format containing a
+     * String of hexadecimal digit characters (two per byte).
+     *
+     * @param bytes Byte array representation
+     */
+    public static String convert(byte bytes[]) {
+
+	StringBuffer sb = new StringBuffer(bytes.length * 2);
+	for (int i = 0; i < bytes.length; i++) {
+	    sb.append(convertDigit((bytes[i] >> 4)));
+	    sb.append(convertDigit((bytes[i] & 0x0f)));
+	}
+	return (sb.toString());
+
+    }
+
+    
+    /**
+     * Convert 4 hex digits to an int, and return the number of converted
+     * bytes.
+     *
+     * @param hex Byte array containing exactly four hexadecimal digits
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is included
+     */
+    public static int convert2Int( byte[] hex ) {
+	// Code from Ajp11, from Apache's JServ
+    
+	// assert b.length==4
+	// assert valid data
+	int len;
+	if(hex.length < 4 ) return 0;
+	if( DEC[hex[0]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len = DEC[hex[0]];
+	len = len << 4;
+	if( DEC[hex[1]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[1]];
+	len = len << 4;
+	if( DEC[hex[2]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[2]];
+	len = len << 4;
+	if( DEC[hex[3]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[3]];
+	return len;
+    }
+
+
+
+    /**
+     * Provide a mechanism for ensuring this class is loaded. 
+     */
+    public static void load() {
+        // Nothing to do
+    }
+
+    /**
+     * [Private] Convert the specified value (0 .. 15) to the corresponding
+     * hexadecimal digit.
+     *
+     * @param value Value to be converted
+     */
+    private static char convertDigit(int value) {
+
+	value &= 0x0f;
+	if (value >= 10)
+	    return ((char) (value - 10 + 'a'));
+	else
+	    return ((char) (value + '0'));
+
+    }
+
+    /**
+     * <code>getHexValue</code> displays a formatted hex
+     * representation of the passed byte array.  It also
+     * allows for only a specified offset and length of 
+     * a particular array to be returned.
+     *
+     * @param bytes <code>byte[]</code> array to process.
+     * @param pos offset to begin processing.
+     * @param len number of bytes to process.
+     * @return <code>String</code> formatted hex representation of processed 
+     *         array.
+     */
+    public static String getHexDump(byte[] bytes, int pos, int len,
+                                     boolean displayOffset) {
+        StringBuffer out = new StringBuffer( len * 2 );
+
+        for (int j = 0; j < len; j += 16) {
+            hexLine(out, bytes, pos + j, pos + len, displayOffset);
+        }
+     
+        return out.toString();
+    }
+    
+    private static void hexLine(StringBuffer out, 
+                                byte[] bytes, int start, int end,
+                                boolean displayOffset) {
+
+        if ( displayOffset ) {
+            out.append(convertDigit((int) (start >> 12)));
+            out.append(convertDigit((int) (start >> 8)));
+            out.append(convertDigit((int) (start >> 4)));
+            out.append(convertDigit(start & 0x0F));
+            out.append(": ");
+        }
+        for (int i = start; i < start + 16; i++) {
+
+            if (i < end) {
+                out.append(convertDigit((int) (bytes[i] >> 4)));
+                out.append(convertDigit(bytes[i] & 0x0F));
+                out.append(" ");
+            } else { 
+                out.append("   ");
+            }
+        }
+        
+        out.append(" | ");
+        
+        for (int i = start; i < start + 16 && i < end; i++) {
+            if( ! Character.isISOControl( (char)bytes[i] )) {
+                out.append( new Character((char)bytes[i]) );
+            } else {
+                out.append( "." );
+            }
+        }
+        
+        out.append("\n");
+    }
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,666 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.FutureCallbacks;
+
+// TODO: append() will trigger callbacks - do it explicitely !!!
+// TODO: queue() shouldn't modify the buffer 
+
+
+/** 
+ * A list of data buckets.
+ * 
+ * @author Costin Manolache
+ */
+public class IOBuffer {
+    static Logger log = Logger.getLogger("IOBrigade");
+    
+    static int ALLOC_SIZE = 8192;
+    long defaultTimeout = Long.MAX_VALUE;
+    
+    private LinkedList<BBucket> buffers = new LinkedList<BBucket>();
+    
+    // close() has been called for out,
+    // or EOF/FIN received for in. It may still have data.
+    boolean closeQueued; 
+    
+    // Will be signalled (open) when there is data in the buffer.
+    // also used to sync on.
+    FutureCallbacks<IOBuffer> hasDataLock = new FutureCallbacks<IOBuffer>() {
+        protected boolean isSignaled() {
+            return hasData();
+        }
+    };
+
+    // may be null
+    protected IOChannel ch;
+    
+    // Support for appending - needs improvements.
+    // appendable buffer is part of the buffer list if it has 
+    // data, and kept here if empty.
+    BBuffer appendable;
+    boolean appending = false;
+    ByteBuffer writeBuffer;
+    
+
+    public IOBuffer() {
+    }
+    
+    public IOBuffer(IOChannel ch) {
+        this.ch = ch;
+    }
+    
+    public IOChannel getChannel() {
+        return ch;
+    }
+    
+    // ===== Buffer access =====
+    
+    BBucket first;
+    
+    /**
+     * Return first non-empty buffer.
+     * 
+     * The append buffer is part of the buffer list, and is left alone and
+     * empty.
+     * 
+     * @return
+     */
+    public BBucket peekFirst() {
+        synchronized (buffers) {
+            BBucket o = (buffers.size() == 0) ? null : buffers.getFirst();
+            
+            while (true) {
+                boolean empty = o == null || isEmpty(o);
+                if (o == null) {
+                    //hasDataLock.reset();
+                    return null; // no data in buffers
+                }
+                // o != null
+                if (empty) {
+                    buffers.removeFirst();
+                    o = (buffers.size() == 0) ? null : buffers.getFirst();                    
+                } else {
+                    first = o;
+                    return o;
+                }
+            }
+        }
+    }
+    
+    public BBucket peekBucket(int idx) {
+        synchronized (buffers) {
+            return buffers.get(idx);
+        }
+    }
+    
+
+    public void advance(int len) {
+        first.position(first.position() + len);
+    }
+
+    public void queue(String s) throws IOException {
+        // TODO: decode with prober charset
+        byte[] bytes = s.getBytes("UTF8");
+        queueInternal(BBuffer.wrapper(bytes, 0, bytes.length));
+    }
+    
+    public void queue(BBuffer bc) throws IOException {
+        queueInternal(bc);
+    }
+    
+    public void queue(Object bb) throws IOException {
+        queueInternal(bb);
+    }
+    
+    private void queueInternal(Object bb) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        synchronized (buffers) {
+            if (appending) {
+                throw new RuntimeException("Unexpected queue while " +
+                                "appending");
+            }
+            BBucket add = wrap(bb);
+            buffers.add(add);
+            //log.info("QUEUED: " + add.remaining() + " " + this);
+            notifyDataAvailable(add);
+        }
+        
+    }
+    
+    public int getBufferCount() {
+        peekFirst();
+        synchronized (buffers) {
+            return buffers.size();
+        }
+    }
+    
+    public void clear() {
+        synchronized (buffers) {
+            buffers.clear();
+        }
+    }
+    
+    public void recycle() {
+        closeQueued = false;
+        clear();
+        // Normally unlocked
+        hasDataLock.recycle();
+
+        appending = false;
+        appendable = null;
+    }
+    
+    // ===================
+    /**
+     * Closed for append. It may still have data.
+     * @return
+     */
+    public boolean isClosedAndEmpty() {
+        return closeQueued && 0 == getBufferCount();
+    }
+
+    
+    /** 
+     * Mark as closed - but will not send data.
+     */
+    public void close() throws IOException {
+        if (closeQueued) {
+            return;
+        }
+        closeQueued = true;
+        notifyDataAvailable(null);
+    }
+    
+    
+    private boolean isEmpty(BBucket o) {
+        if (o instanceof BBucket && 
+                ((BBucket) o).remaining() == 0) {
+            return true;
+        }        
+        return false;
+    }
+    
+    private BBucket wrap(Object src) {
+        if (src instanceof byte[]) {
+            return BBuffer.wrapper((byte[]) src, 0, ((byte[]) src).length);
+        }
+        if (src instanceof ByteBuffer) {
+            //return src;
+            ByteBuffer bb = (ByteBuffer) src;
+            return BBuffer.wrapper(bb.array(), bb.position(), 
+                        bb.remaining());            
+        }
+        if (src instanceof byte[]) {
+            byte[] bb = (byte[]) src;
+            return BBuffer.wrapper(bb, 0, bb.length); 
+        }
+        return (BBucket) src;
+    }
+    
+    protected void notifyDataAvailable(Object bb) throws IOException {
+        synchronized (hasDataLock) {
+            hasDataLock.signal(this); // or bb ?
+        }
+    }
+    
+    public boolean hasData() {
+        return closeQueued || peekFirst() != null; 
+    }
+    
+    public void waitData(long timeMs) throws IOException {
+        if (timeMs == 0) {
+            timeMs = defaultTimeout;
+        }
+        long exp = (timeMs == Long.MAX_VALUE) ?
+                Long.MAX_VALUE : System.currentTimeMillis() + timeMs;
+        synchronized (hasDataLock) {
+            if (hasData()) {
+                return;
+            }
+            hasDataLock.reset();
+        }
+        if (timeMs == 0) {
+            timeMs = Long.MAX_VALUE;
+        }
+        long wait = (timeMs == Long.MAX_VALUE) ? Long.MAX_VALUE : 
+            exp - System.currentTimeMillis();
+
+        hasDataLock.waitSignal(wait);
+        if (exp < System.currentTimeMillis()) {
+            throw new IOException("Timeout");
+        }
+    }
+    
+
+    
+    public boolean isAppendClosed() {
+        return closeQueued;
+    }
+
+    // =================== Helper methods ==================
+
+    /**
+     * Non-blocking.
+     */
+    public int read() throws IOException {
+        BBucket bucket = peekFirst();
+        if (bucket == null) {
+            return -1;
+        }
+        int res = bucket.array()[bucket.position()];
+        bucket.position(bucket.position() + 1);
+        return res;
+    }
+    
+    public int peek() throws IOException {
+        BBucket bucket = peekFirst();
+        if (bucket == null) {
+            return -1;
+        }
+        int res = bucket.array()[bucket.position()];
+        return res;
+    }
+    
+    public int find(char c) {
+        int pos = 0;
+        for (int i = 0; i < buffers.size(); i++) {
+            BBucket bucket = buffers.get(i);
+            if (bucket == null || bucket.remaining() == 0) {
+                continue;
+            }
+            int found= BBuffer.findChar(bucket.array(), bucket.position(),
+                    bucket.limit(), c);
+            if (found >= 0) {
+                return pos + found; 
+            }
+            pos += bucket.remaining();
+        }
+        return -1;
+    }
+    
+    public int readLine(BBuffer bc) throws IOException {
+        return readToDelim(bc, '\n');
+    }
+    
+    /**
+     * Copy up to and including "delim". 
+     * 
+     * @return number of bytes read, or -1 for end of stream.
+     */
+    int readToDelim(BBuffer bc, int delim) throws IOException {
+        int len = 0;
+        for (int idx = 0; idx < buffers.size(); idx++) {
+            BBucket bucket = buffers.get(idx);
+            if (bucket == null || bucket.remaining() == 0) {
+                continue;
+            }
+            byte[] data = bucket.array();
+            int end = bucket.limit();
+            int start = bucket.position();
+            for (int i = start; i < end; i++) {
+                byte chr = data[i];
+                bc.put(chr);
+                if (chr == delim) {
+                    bucket.position(i + 1);
+                    len += (i - start + 1); 
+                    return len;
+                }
+            }
+            bucket.position(end); // empty - should be removed
+        }
+        if (len == 0 && isClosedAndEmpty()) {
+            return -1;
+        }
+        return len;
+    }
+    
+    
+    public int write(ByteBuffer bb) throws IOException {
+        int len = bb.remaining();
+        append(bb);
+        bb.position(bb.position() + len);
+        return len;
+    }
+    
+    public int read(byte[] buf, int off, int len) throws IOException {
+        BBucket bucket = peekFirst();
+        if (isClosedAndEmpty()) {
+            return -1;
+        }
+        if (bucket == null) {
+            return 0;
+        }
+        int toCopy = Math.min(len, bucket.remaining());
+        System.arraycopy(bucket.array(), bucket.position(), buf, 
+                off, toCopy);
+        bucket.position(bucket.position() + toCopy);
+        return toCopy;
+        
+    }
+
+    /**
+     * Non-blocking read.
+     */
+    public int read(ByteBuffer bb) {
+        if (isClosedAndEmpty()) {
+            return -1;
+        }
+        int len = 0;
+        while (true) {
+            int space = bb.remaining(); // to append
+            if (space == 0) {
+                return len;
+            }
+            BBucket first = peekFirst();
+            if (first == null) {
+                return len;
+            }
+            BBucket iob = ((BBucket) first);
+            if (space > iob.remaining()) {
+                space = iob.remaining();
+            }
+            bb.put(iob.array(), iob.position(), space);
+
+            iob.position(iob.position() + space);
+            iob.release();
+            len += space;
+        }        
+    }
+    
+
+    public BBuffer readAll(BBuffer chunk) throws IOException {
+        if (chunk == null) {
+            chunk = allocate();
+        }
+        while (true) {
+            if (isClosedAndEmpty()) {
+                return chunk;
+            }
+            BBucket first = peekFirst();
+            if (first == null) {
+                return chunk;
+            }
+            BBucket iob = ((BBucket) first);
+            chunk.append(iob.array(), iob.position(), iob.remaining());
+            iob.position(iob.position() + iob.remaining());
+            iob.release();
+    
+        }
+    }
+
+    private BBuffer allocate() {
+        int size = 0;
+        for (int i = 0; i < getBufferCount(); i++) { 
+            BBucket first = peekBucket(i);
+            if (first != null) {
+                size += first.remaining();
+            }
+        }
+        return BBuffer.allocate(size);
+    }
+    
+    public BBuffer copyAll(BBuffer chunk) throws IOException {
+        if (chunk == null) {
+            chunk = allocate();
+        }
+        for (int i = 0; i < getBufferCount(); i++) { 
+            BBucket iob = peekBucket(i);
+            chunk.append(iob.array(), iob.position(), iob.remaining());
+        }
+        return chunk;
+    }
+
+    public IOBuffer append(InputStream is) throws IOException {
+        while (true) {
+            ByteBuffer bb = getWriteBuffer();
+            int rd = is.read(bb.array(), bb.position(), bb.remaining());
+            if (rd <= 0) {
+                return this;
+            }
+            bb.position(bb.position() + rd);
+            releaseWriteBuffer(rd);
+        }
+    }
+
+    public IOBuffer append(BBuffer bc) throws IOException {
+        return append(bc.array(), bc.getStart(), bc.getLength());
+    }
+
+    public IOBuffer append(byte[] data) throws IOException {
+        return append(data, 0, data.length);
+    }
+    
+    public IOBuffer append(byte[] data, int start, int len) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        ByteBuffer bb = getWriteBuffer();
+        
+        int i = start;
+        int end = start + len;
+        while (i < end) {
+            int rem = Math.min(end - i, bb.remaining()); 
+            // to write
+            bb.put(data, i, rem);
+            i += rem; 
+            if (bb.remaining() < 8) {
+                releaseWriteBuffer(1);
+                bb = getWriteBuffer();
+            }
+        }
+
+        releaseWriteBuffer(1);
+        return this;        
+    }
+    
+    public IOBuffer append(int data) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        ByteBuffer bb = getWriteBuffer();
+        bb.put((byte) data);
+        releaseWriteBuffer(1);
+        return this;        
+    }
+    
+    public IOBuffer append(ByteBuffer cs) throws IOException {
+        return append(cs.array(), cs.position() + cs.arrayOffset(), 
+                cs.remaining());
+    }
+    
+    public IOBuffer append(BBucket cs) throws IOException {
+        append(cs.array(), cs.position(), cs.remaining());
+        return this;
+    }
+    
+    public IOBuffer append(IOBuffer cs) throws IOException {
+        for (int i = 0; i < cs.getBufferCount(); i++) {
+            Object o = cs.peekBucket(i);
+            if (o instanceof BBucket) {
+                append((BBucket)o);
+            } else if (o instanceof ByteBuffer) {
+                append((ByteBuffer) o);
+            } else if (o instanceof CharSequence) {
+                append((CharSequence) o);                
+            } else {
+                throw new IOException("Unknown type " + o);
+            }
+        }
+
+        return this;        
+    }
+    
+    public IOBuffer append(CharSequence cs) throws IOException {
+        byte[] data = cs.toString().getBytes();
+        append(data, 0, data.length);
+        return this;
+    }
+    
+    public IOBuffer append(char c) throws IOException {
+        ByteBuffer bb = getWriteBuffer();
+        bb.put((byte) c);
+        releaseWriteBuffer(1);
+        return this;        
+    }
+    
+    /**
+     * All operations that iterate over buffers must be 
+     * sync
+     * @return
+     */
+    public synchronized int available() {
+        int a = 0;
+        int cnt = buffers.size();
+        for (int i = 0; i < cnt; i++) {
+            a += buffers.get(i).remaining();
+        }
+        return a;
+    }
+    
+    public String toString() {
+        return "IOB:{c:" + getBufferCount() +
+          ", b:" + available() +
+          (isAppendClosed() ? ", C}" : " }");
+    }
+
+    public BBucket popLen(int lenToConsume) {
+        BBucket o = peekFirst(); // skip empty
+        if (o == null) {
+            return null;
+        }
+        BBucket sb = BBuffer.wrapper(o.array(),
+                o.position(), lenToConsume);
+        o.position(o.position() + lenToConsume);
+        return sb;
+    }
+    
+    public BBucket popFirst() {
+        BBucket o = peekFirst(); // skip empty
+        if (o == null) {
+            return null;
+        }
+        if (o == appendable) {
+            synchronized (buffers) {
+                    // TODO: concurrency ???
+                    BBucket sb = 
+                        BBuffer.wrapper(appendable.array(),
+                                appendable.position(), 
+                                appendable.limit() - appendable.position());
+                    appendable.position(appendable.limit());
+                    return sb;
+            }
+        } else {
+            buffers.removeFirst();
+        }
+        return o;
+    }
+    
+
+    public ByteBuffer getWriteBuffer() throws IOException {
+        synchronized (buffers) {
+            if (closeQueued) {
+                throw new IOException("Closed");
+            }
+            synchronized (buffers) {
+                BBucket last = (buffers.size() == 0) ? 
+                        null : buffers.getLast();
+                if (last == null || last != appendable ||
+                        last.array().length - last.limit() < 16) {
+                    last = BBuffer.allocate(ALLOC_SIZE);
+                }
+                appending = true;
+                appendable = (BBuffer) last;            
+            }
+            
+            if (writeBuffer == null || writeBuffer.array() != appendable.array()) {
+                writeBuffer = ByteBuffer.wrap(appendable.array());
+            }
+            writeBuffer.position(appendable.limit());
+            writeBuffer.limit(appendable.array().length);
+            return writeBuffer;
+        }
+    }        
+    
+    public void releaseWriteBuffer(int read) throws IOException {
+        if (!appending) {
+            throw new IOException("Not appending");
+        }
+        synchronized (buffers) {
+            if (writeBuffer != null) {
+                if (read > 0) {
+                    appendable.limit(writeBuffer.position());
+                    // We have some more data.
+                    if (buffers.size() == 0 || 
+                            buffers.getLast() != appendable) {
+                        buffers.add(appendable);
+                    }
+                    notifyDataAvailable(appendable);                
+                }
+            }
+        }
+        appending = false;
+    }
+
+    
+
+    // ------ More utilities - for parsing request ( later )-------
+//  public final int skipBlank(ByteBuffer bb, int start) {
+//  // Skipping blank lines
+//  byte chr = 0;
+//  do {
+//    if (!bb.hasRemaining()) {
+//      return -1;
+//    }
+//    chr = bb.get();
+//  } while ((chr == HttpParser.CR) || (chr == HttpParser.LF));
+//  return bb.position();
+//}
+
+//public final int readToDelimAndLowerCase(ByteBuffer bb,
+//                                         byte delim, 
+//                                         boolean lower) {
+//  boolean space = false;
+//  byte chr = 0;
+//  while (!space) {
+//    if (!bb.hasRemaining()) {
+//      return -1;
+//    }
+//    chr = bb.get();
+//    if (chr == delim) {
+//      space = true;
+//    }
+//    if (lower && (chr >= HttpParser.A) && (chr <= HttpParser.Z)) {
+//      bb.put(bb.position() - 1, 
+//          (byte) (chr - HttpParser.LC_OFFSET));
+//    }
+//  }
+//  return bb.position();
+//}
+
+//public boolean skipSpace(ByteBuffer bb) {
+//  boolean space = true;
+//  while (space) {
+//    if (!bb.hasRemaining()) {
+//      return false;
+//    }
+//    byte chr = bb.get();
+//    if ((chr == HttpParser.SP) || (chr == HttpParser.HT)) {
+//      //
+//    } else {
+//      space = false;
+//      bb.position(bb.position() -1); // move back
+//    }
+//  }
+//  return true;
+//}
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,290 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+
+
+
+/**
+ * Buffered ByteChannel, backed by a buffer brigade to allow
+ * some zero-copy operations.
+ * 
+ * - you can use it as a normal non-blocking ByteChannel.
+ * - you can call getRead
+ * 
+ * Very different from MINA IoFilters, also much lower level.
+ * 
+ * 
+ * @author Costin Manolache
+ */
+public abstract class IOChannel implements ByteChannel, IOConnector.DataReceivedCallback, 
+        IOConnector.DataFlushedCallback { //, IOConnector.ClosedCallback {
+    
+    protected IOChannel net;
+    protected IOChannel app;
+    
+    protected String id;
+    protected String target;    
+
+    protected IOConnector connector;
+
+    protected IOConnector.ConnectedCallback connectedCallback;
+    protected IOConnector.DataReceivedCallback dataReceivedCallback;
+    protected IOConnector.DataFlushedCallback dataFlushedCallback;
+
+    // Last activity timestamp.
+    // TODO: update, etc
+    public long ts;
+    
+    public void setConnectedCallback(IOConnector.ConnectedCallback connectedCallback) {
+        this.connectedCallback = connectedCallback;
+    }
+
+    public void setDataReceivedCallback(IOConnector.DataReceivedCallback dataReceivedCallback) {
+        this.dataReceivedCallback = dataReceivedCallback;
+    }
+
+    /**
+     * Callback called when the bottom ( OS ) channel has finished flushing.
+     * 
+     * @param dataFlushedCallback
+     */
+    public void setDataFlushedCallback(IOConnector.DataFlushedCallback dataFlushedCallback) {
+        this.dataFlushedCallback = dataFlushedCallback;
+    }
+
+    protected IOChannel() {
+    }
+    
+    // Input
+    public abstract IOBuffer getIn();
+
+    // Output 
+    public abstract IOBuffer getOut();
+    
+    
+    /** 
+     * From downstream ( NET ). Pass it to the next channel.
+     */
+    public void handleReceived(IOChannel net) throws IOException {
+        sendHandleReceivedCallback();
+    }
+    
+    /** 
+     * Called from lower layer (NET) when the last flush is 
+     * done and all buffers have been sent to OS ( or 
+     * intended recipient ).
+     * 
+     * Will call the callback or next filter, may do additional
+     * processing.
+     * 
+     * @throws IOException
+     */
+    public void handleFlushed(IOChannel net) throws IOException {
+        sendHandleFlushedCallback();
+    }
+    
+    public void sendHandleFlushedCallback() throws IOException {
+        try {
+            if (dataFlushedCallback != null) {
+                dataFlushedCallback.handleFlushed(this);
+            }
+            if (app != null) {
+                app.handleFlushed(this);
+            }
+        } catch (Throwable t) {
+            close();
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            } else {
+                throw new WrappedException("Error in handleFlushed", t);
+            }
+        } 
+    }
+    
+    
+    /**
+     * Notify next channel that data has been received.  
+     */
+    public void sendHandleReceivedCallback() throws IOException {
+        try {
+            if (dataReceivedCallback != null) {
+                dataReceivedCallback.handleReceived(this);
+            }
+            if (app != null) {
+                app.handleReceived(this);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+            try {
+                close();
+            } catch(Throwable t2) {
+                t2.printStackTrace();
+            }
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            } else {
+                throw new WrappedException(t);
+            }
+        } 
+    }
+  
+    public void close() throws IOException {
+        shutdownOutput();
+        // Should it read the buffers ? 
+        
+        if (getIn().isAppendClosed()) {
+            return;
+        } else {
+            getIn().close();
+            sendHandleReceivedCallback();
+        }
+        getIn().hasDataLock.signal(getIn());
+    }
+
+    public boolean isOpen() {
+        return !getIn().isAppendClosed() && !getOut().isAppendClosed();
+    }
+    
+    public void shutdownOutput() throws IOException {
+        if (getOut().isAppendClosed()) {
+            return;
+        } else {
+            getOut().close();
+            startSending();
+        }
+    }
+
+    public void setSink(IOChannel previous) {
+        this.net = previous;
+    }
+
+    public IOChannel getSink() {
+        return net;
+    }
+
+    // Chaining/filtering
+    
+    /** 
+     * Called to add an filter _after_ the current channel.
+     */
+    public IOChannel addFilterAfter(IOChannel next) {
+        this.app = next;
+        app.setSink(this);
+
+        // TODO: do we want to migrate them automatically ?
+        app.setDataReceivedCallback(dataReceivedCallback);
+        app.setDataFlushedCallback(dataFlushedCallback);
+        // app.setClosedCallback(closedCallback);
+
+        dataReceivedCallback = null;
+        dataFlushedCallback = null;
+        return this;
+    }
+
+    public IOChannel getFirst() {
+        IOChannel first = this;
+        while (true) {
+            if (!(first instanceof IOChannel)) {
+                return first;
+            }
+            IOChannel before = ((IOChannel) first).getSink();
+            if (before == null) {
+                return first;
+            } else {
+                first = before;
+            }
+        }
+    }
+    
+    // Socket support
+    
+    public int getPort(boolean remote) {
+        if (net != null) {
+            return net.getPort(remote);
+        }
+        return 80;
+    }
+    
+    public void readInterest(boolean b) throws IOException {
+        if (net != null) {
+            net.readInterest(b);
+        }
+    }
+    
+    // Helpers
+
+    public int read(ByteBuffer bb) throws IOException {
+        return getIn().read(bb);
+    }
+    
+    public int readNonBlocking(ByteBuffer bb) throws IOException {
+        return getIn().read(bb);
+    }
+
+    public int readBlocking(ByteBuffer bb, long timeMs) throws IOException {
+        getIn().waitData(timeMs);
+        return getIn().read(bb);
+    }
+    
+    /** 
+     * Capture all output in a buffer. 
+     */
+    public BBuffer readAll(BBuffer chunk, long to) 
+            throws IOException {
+        if (chunk == null) {
+            chunk = BBuffer.allocate();
+        }
+        while (true) { 
+            getIn().waitData(to);
+            BBucket next = getIn().peekFirst();
+            if (getIn().isClosedAndEmpty() && next == null) {
+                return chunk;
+            }
+            if (next == null) {
+                continue; // false positive
+            }
+            chunk.append(next.array(), next.position(), next.remaining());
+            getIn().advance(next.remaining());
+        }
+    }
+    
+    public int write(ByteBuffer bb) throws IOException {
+        return getOut().write(bb);
+    }
+    
+    public void write(byte[] data) throws IOException {
+        getOut().append(data, 0, data.length);
+    }
+
+    public void write(String string) throws IOException {
+        write(string.getBytes());
+    }
+    
+    /** 
+     * Send data in out to the intended recipient.
+     * This is not blocking.
+     */
+    public abstract void startSending() throws IOException;
+
+    
+    public void setId(String id) {
+        this.id = id;
+    }
+    
+    public String getId() {
+        return id;
+    }
+    
+    public CharSequence getTarget() {
+        if (net != null) {
+            return net.getTarget();
+        }
+        return target;
+    }
+    
+    
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,42 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+
+/**
+ * Factory for IOChannels, with support for caching.
+ * 
+ * 
+ * @author Costin Manolache
+ */
+public abstract class IOConnector {
+
+    public static interface DataReceivedCallback {
+        /** 
+         * Called when data or EOF has been received.
+         */
+        public void handleReceived(IOChannel ch) throws IOException;
+    }
+
+    public static interface ConnectedCallback {
+        public void handleConnected(IOChannel ch) throws IOException;
+    }
+
+    public static interface DataFlushedCallback {
+        public void handleFlushed(IOChannel ch) throws IOException;
+    }
+    
+    public abstract void acceptor(IOConnector.ConnectedCallback sc, 
+                         CharSequence port, Object extra)
+        throws IOException; 
+
+    // TODO: failures ? 
+    public abstract void connect(String host, int port, 
+                                      IOConnector.ConnectedCallback sc) throws IOException;
+    
+    public void stop() {
+        
+    }
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,71 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Similar with ServletInputStream - adds readLine(byte[]..), using
+ * a IOBuffer.
+ * 
+ * 
+ * 
+ * @author Costin Manolache
+ */
+public class IOInputStream extends InputStream {
+
+    IOBuffer bb;
+    long timeout;
+    
+    public IOInputStream(IOChannel httpCh, long to) {
+        bb = httpCh.getIn();
+        this.timeout = to;
+    }
+    
+    @Override
+    public int read() throws IOException {
+        // getReadableBucket/peekFirst returns a buffer with at least
+        // 1 byte in it.
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        bb.waitData(timeout);
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        
+        return bb.read();
+    }
+    
+    public int read(byte[] buf, int off, int len) throws IOException {
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        bb.waitData(timeout);
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        return bb.read(buf, off, len);
+    }
+
+    /**
+     *  Servlet-style read line: terminator is \n or \r\n, left in buffer.
+     */
+    public int readLine(byte[] b, int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int count = 0, c;
+
+        while ((c = read()) != -1) {
+            b[off++] = (byte)c;
+            count++;
+            if (c == '\n' || count == len) {
+                break;
+            }
+        }
+        return count > 0 ? count : -1;
+    }
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,207 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+
+import org.apache.tomcat.lite.http.HttpMessage;
+import org.apache.tomcat.lite.http.HttpWriter;
+
+/**
+ * Same methods with ServletOutputStream.
+ * 
+ * There is no restriction in using the Writer and InputStream at the 
+ * same time - the servlet layer will impose it for compat. You can also use 
+ * IOBuffer directly. 
+ * 
+ * If you mix stream and writer:
+ *  - call BufferWriter.push() to make sure all chars are sent down
+ *  - the BufferOutputStream doesn't cache any data, all goes to the 
+ *   IOBuffer.
+ *  - flush() on BufferOutputStream and BufferWriter will send the data
+ *  to the network and block until it gets to the socket ( so it can
+ *  throw exception ). 
+ *  - You can also use non-blocking flush methods in IOBuffer, and a
+ *  callback  if you want to know when the write was completed.
+ *    
+ * @author Costin Manolache
+ */
+public class IOOutputStream extends OutputStream {
+
+    IOBuffer bb;
+    HttpMessage message;
+    int bufferSize = HttpWriter.DEFAULT_BUFFER_SIZE;
+    
+    int wSinceFlush = 0;
+    
+    public IOOutputStream(IOBuffer out, HttpMessage httpMessage) {
+        this.bb = out;
+        message = httpMessage;
+    }
+
+    public void recycle() {
+        wSinceFlush = 0;
+        bufferSize = HttpWriter.DEFAULT_BUFFER_SIZE;
+    }
+    
+    public void reset() {
+        wSinceFlush = 0;
+        bb.clear();
+    }
+
+    public int getWrittenSinceFlush() {
+        return wSinceFlush;
+    }
+
+
+    public int getBufferSize() {
+        return bufferSize;
+    }
+
+    public void setBufferSize(int size) {
+        if (size > bufferSize) {
+            bufferSize = size;
+        }
+    }
+
+    private void updateSize(int cnt) throws IOException {
+        wSinceFlush += cnt;
+        if (wSinceFlush > bufferSize) {
+            flush();
+        }
+    }
+    
+    @Override
+    public void write(int b) throws IOException {
+        bb.append((char) b);
+        updateSize(1);
+    }
+    
+    @Override
+    public void write(byte data[]) throws IOException {
+      write(data, 0, data.length);
+    }    
+
+    @Override
+    public void write(byte data[], int start, int len) throws IOException {
+        bb.append(data, start, len);
+        updateSize(len);
+    }    
+    
+    public void flush() throws IOException {
+        if (message.getHttpChannel() != null) {
+            message.getHttpChannel().startSending();
+            
+            message.getHttpChannel().waitFlush(Long.MAX_VALUE);
+        }
+        wSinceFlush = 0;
+    }
+
+    public void close() throws IOException {
+        flush();
+        bb.close();
+    }
+    
+
+    public void write(ByteBuffer source) throws IOException {
+        write(source.array(), source.position(), source.remaining());
+        source.position(source.limit());
+    }
+    
+    public void print(String s) throws IOException {
+        if (s==null) s="null";
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt (i);
+
+            //
+            // XXX NOTE:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            //
+            if ((c & 0xff00) != 0) {    // high order byte must be zero
+                String errMsg = "Not ISO-8859-1";
+                Object[] errArgs = new Object[1];
+                errArgs[0] = new Character(c);
+                errMsg = MessageFormat.format(errMsg, errArgs);
+                throw new CharConversionException(errMsg);
+            }
+            write (c);
+        }
+    }
+
+
+    public void print(boolean b) throws IOException {
+        String msg;
+        if (b) {
+            msg = "true";
+        } else {
+            msg = "false";
+        }
+        print(msg);
+    }
+
+    public void print(char c) throws IOException {
+        print(String.valueOf(c));
+    }
+
+    public void print(int i) throws IOException {
+        print(String.valueOf(i));
+    }
+
+    public void print(long l) throws IOException {
+        print(String.valueOf(l));
+    }
+
+    public void print(float f) throws IOException {
+        print(String.valueOf(f));
+    }
+
+    public void print(double d) throws IOException {
+        print(String.valueOf(d));
+    }
+
+    public void println() throws IOException {
+        print("\r\n");
+    }
+
+    public void println(String s) throws IOException {
+        print(s);
+        println();
+    }
+
+    public void println(boolean b) throws IOException {
+        print(b);
+        println();
+    }
+
+    public void println(char c) throws IOException {
+        print(c);
+        println();
+    }
+
+    public void println(int i) throws IOException {
+        print(i);
+        println();
+    }
+
+    public void println(long l) throws IOException {
+        print(l);
+        println();
+    }
+
+    public void println(float f) throws IOException {
+        print(f);
+        println();
+    }
+
+    public void println(double d) throws IOException {
+        print(d);
+        println();
+    }
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,231 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.UnmappableCharacterException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Conversion from Bytes to Chars and support for decoding.
+ * 
+ * Replaces tomcat B2CConverter with NIO equivalent. B2CConverter was a hack 
+ * (re)using an dummy InputStream backed by a ByteChunk. 
+ * 
+ * @author Costin Manolache
+ */
+public class IOReader extends Reader {
+    
+    IOBuffer iob;
+    Map<String, CharsetDecoder> decoders = new HashMap<String, CharsetDecoder>();
+    CharsetDecoder decoder;
+    
+    private static boolean REUSE = true;
+    String enc;
+    private boolean closed;
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+    
+    public IOReader(IOBuffer iob) {
+        this.iob = iob;
+    }
+    
+    public void setEncoding(String charset) {
+        enc = charset;
+        if (enc == null) {
+            enc = DEFAULT_ENCODING;
+        }
+        decoder = REUSE ? decoders.get(enc) : null;
+        if (decoder == null) {
+            decoder = Charset.forName(enc).newDecoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                decoders.put(enc, decoder);
+            }
+        }
+    }
+    
+    public String getEncoding() {
+        return enc;
+    }
+    
+    public void recycle() {
+        if (decoder != null) {
+            decoder.reset();
+        }
+        closed = false;
+        enc = null;
+    }
+    
+    private void checkClosed() throws IOException {
+        if (closed) throw new IOException("closed");
+    }
+    
+    public boolean ready() {
+        return iob.peekFirst() != null;
+    }
+
+    public int read(java.nio.CharBuffer target) throws IOException {
+        int len = target.remaining();
+        char[] cbuf = new char[len];
+        int n = read(cbuf, 0, len);
+        if (n > 0)
+            target.put(cbuf, 0, n);
+        return n;
+    }
+
+    public int read() throws IOException {
+        char cb[] = new char[1];
+        if (read(cb, 0, 1) == -1)
+            return -1;
+        else
+            return cb[0];
+    }
+    
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        iob.close();
+    }
+    
+    /** 
+     * Used if a bucket ends on a char boundary
+     */
+    BBuffer underFlowBuffer = BBuffer.allocate(10);
+    public static AtomicInteger underFlows = new AtomicInteger();
+
+    /**
+     * Decode all bytes - for example a URL or header.
+     */
+    public void decodeAll(BBucket bb, CBuffer c) {
+        
+        while (bb.hasRemaining()) {
+            CharBuffer charBuffer = c.getAppendCharBuffer();
+            CoderResult res = decode1(bb, charBuffer, true);
+            c.returnAppendCharBuffer(charBuffer);
+            if (res != CoderResult.OVERFLOW) {
+                if (res == CoderResult.UNDERFLOW || bb.hasRemaining()) {
+                    System.err.println("Ignored trailing bytes " + bb.remaining());
+                } 
+                return;
+            }
+        }
+        
+    }
+    
+    /** 
+     * Do one decode pass.  
+     */
+    public CoderResult decode1(BBucket bb, CharBuffer c, boolean eof) {
+        ByteBuffer b = bb.getByteBuffer();
+
+        if (underFlowBuffer.hasRemaining()) {
+            // Need to get rid of the underFlow first
+            for (int i = 0; i < 10; i++) {
+                underFlowBuffer.put(b.get());
+                bb.position(b.position());
+                ByteBuffer ub = underFlowBuffer.getByteBuffer();
+                CoderResult res = decoder.decode(ub, c, eof);
+                if (! ub.hasRemaining()) {
+                    // underflow resolved
+                    break;
+                }
+                if (res == CoderResult.OVERFLOW) {
+                    return res;
+                }
+            }
+            if (underFlowBuffer.hasRemaining()) {
+                throw new RuntimeException("Can't resolve underflow after " +
+                		"10 bytes");
+            }
+        }
+        
+        CoderResult res = decoder.decode(b, c, eof);
+        bb.position(b.position());
+        
+        if (res == CoderResult.UNDERFLOW && bb.hasRemaining()) {
+            // b ends on a boundary
+            underFlowBuffer.append(bb.array(), bb.position(), bb.remaining());
+            bb.position(bb.limit());
+        } 
+        return res;
+    }
+    
+    @Override
+    public int read(char[] cbuf, int offset, int length) throws IOException {
+        checkClosed();
+        if (length == 0) {
+            return 0;
+        }
+        // we can either allocate a new CharBuffer or use a 
+        // static one and copy. Seems simpler this way - needs some
+        // load test, but InputStreamReader seems to do the same.
+        CharBuffer out = CharBuffer.wrap(cbuf, offset, length);
+        
+        CoderResult result = CoderResult.UNDERFLOW;
+
+        BBucket bucket = iob.peekFirst();
+        
+        // Consume as much as possible without blocking
+        while (result == CoderResult.UNDERFLOW) {
+            // fill the buffer if needed
+            if (bucket == null || ! bucket.hasRemaining()) {
+                if (out.position() > offset) {
+                    // we could return the result without blocking read
+                    break;
+                }
+                bucket = null;
+                while (bucket == null) {
+                    iob.waitData(0);
+                    bucket = iob.peekFirst();
+                    if (bucket == null && iob.isClosedAndEmpty()) {
+                        // EOF, we couldn't decode anything
+                        break;
+                    }
+                }
+                
+                if (bucket == null) {
+                    // eof
+                    break;
+                }
+            }
+
+            result = decode1(bucket, out, false);
+        }
+
+        if (result == CoderResult.UNDERFLOW && iob.isClosedAndEmpty()) {
+            // Flush out any remaining data
+            ByteBuffer bytes = bucket == null ? 
+                    underFlowBuffer.getByteBuffer() : bucket.getByteBuffer();
+            result = decoder.decode(bytes, out, true);
+            if (bucket == null) {
+                underFlowBuffer.position(bytes.position());
+            } else {
+                bucket.position(bytes.position());
+            }
+
+            decoder.flush(out);
+            decoder.reset();
+        }
+
+        if (result.isMalformed()) {
+            throw new MalformedInputException(result.length());
+        } else if (result.isUnmappable()) {
+            throw new UnmappableCharacterException(result.length());
+        }
+
+        int rd = out.position() - offset;
+        return rd == 0 ? -1 : rd;
+    }
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,180 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts chars to bytes, and associated encoding.
+ * 
+ * Replaces C2B from old tomcat. 
+ * 
+ * @author Costin Manolache
+ */
+public class IOWriter extends Writer {
+    
+    IOBuffer iob;
+    Map<String, CharsetEncoder> encoders = new HashMap<String, CharsetEncoder>();
+    CharsetEncoder encoder;
+    
+    private static boolean REUSE = true;
+    String enc;
+    private boolean closed;
+    IOChannel ioCh;
+    
+    public IOWriter(IOChannel iob) {
+        this.ioCh = iob;
+        if (iob != null) {
+            this.iob = iob.getOut();
+        }
+    }
+    
+    public void setEncoding(String charset) {
+        if (charset == null) {
+            charset = "UTF-8"; 
+        }
+        enc = charset;
+        encoder = getEncoder(charset);
+        if (encoder == null) {
+            encoder = Charset.forName(charset).newEncoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                encoders.put(charset, encoder);
+            }
+        }
+    }
+    
+    CharsetEncoder getEncoder(String charset) {
+        if (charset == null) {
+            charset = "UTF-8"; 
+        }
+        encoder = REUSE ? encoders.get(charset) : null;
+        if (encoder == null) {
+            encoder = Charset.forName(charset).newEncoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                encoders.put(charset, encoder);
+            }
+        }
+        return encoder;
+    }
+    
+    public String getEncoding() {
+        return enc;
+    }
+    
+    public void recycle() {
+        if (encoder != null) {
+            encoder.reset();
+        }
+        closed = false;
+        enc = null;
+    }
+    
+    
+    private void checkClosed() throws IOException {
+        if (closed) throw new IOException("closed");
+    }
+    
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        // flush the buffer ?
+        ByteBuffer out = iob.getWriteBuffer();
+        encoder.flush(out);
+        iob.releaseWriteBuffer(1);
+        
+        iob.close();
+    }
+    
+    /** 
+     * Used if a bucket ends on a char boundary
+     */
+    CBuffer underFlowBuffer = CBuffer.newInstance();
+
+    public void encode1(CBuffer cc, 
+            BBuffer bb, CharsetEncoder encoder, boolean eof) {
+        CharBuffer c = cc.getNioBuffer();
+        ByteBuffer b = bb.getWriteByteBuffer(c.remaining() * 2);
+        encode1(c, b, encoder, eof);
+        cc.returnNioBuffer(c);
+        bb.limit(b.position());
+    }
+
+    /** 
+     * 
+     * @param cc
+     * @return
+     */
+    public void encode1(CharBuffer c, 
+            ByteBuffer b, CharsetEncoder encoder, boolean eof) {
+        
+        // TODO: buffer growth in caller
+        
+        CoderResult res = encoder.encode(c, b, eof);
+        if (res == CoderResult.OVERFLOW) {
+            // bb is full - next call will get a larger buffer ( it 
+            // grows ) or maybe will be flushed.
+        }
+        if (res == CoderResult.UNDERFLOW && c.remaining() > 0 && !eof) {
+            // TODO: if eof -> exception ?
+            // cc has remaining chars - for example a surrogate start.
+            underFlowBuffer.put(c);
+        } 
+        
+    }
+
+    public void encodeAll(CBuffer cc, 
+            BBuffer bb, CharsetEncoder encoder, boolean eof) {
+        while (cc.length() > 0) {
+            encode1(cc, bb, encoder, eof);
+        }
+    }    
+
+    public void encodeAll(CBuffer cc, 
+            BBuffer bb, String cs) {
+        encodeAll(cc, bb, getEncoder(cs), true);
+    }    
+    
+    @Override
+    public void flush() throws IOException {
+        if (ioCh != null) {
+            ioCh.startSending();
+        }
+    }
+
+    /**
+     * Just send the chars to the byte[], without flushing down.
+     * 
+     * @throws IOException
+     */
+    public void push() throws IOException {
+        // we don't cache here.
+    }
+    
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        checkClosed();
+        CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
+        
+        while (cb.remaining() > 0) {
+            ByteBuffer wb = iob.getWriteBuffer();
+            encode1(cb, wb, encoder, false);
+            iob.releaseWriteBuffer(1);
+        }
+    }
+    
+    
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,83 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+public class MemoryIOConnector extends IOConnector {
+
+    public static class MemoryIOChannel extends IOChannel {
+        IOBuffer netIn = new IOBuffer(this) {
+            protected void notifyDataAvailable(Object bb) throws IOException {
+                sendHandleReceivedCallback();
+                super.notifyDataAvailable(bb);
+            }
+        };
+        IOBuffer netOut = new IOBuffer(this);
+        
+        /**
+         * All flushed output will be saved to 'out'.
+         */
+        public BBuffer out = BBuffer.allocate(4096);
+        
+        public MemoryIOChannel() {
+        }
+        
+        public void startSending() throws IOException {
+            //
+            IOBuffer bb = netOut;
+            while (true) {
+                if (bb.isClosedAndEmpty()) {
+                    break;
+                }
+                BBucket first = bb.peekFirst();
+                if (first == null) {
+                    break;
+                }
+                BBucket iob = ((BBucket) first);
+                out.append(iob.array(), iob.position(), iob.remaining());
+                bb.advance(iob.remaining());
+                iob.release();
+            }
+
+            handleFlushed(this);
+        }
+        
+        @Override
+        public IOBuffer getIn() {
+            return netIn;
+        }
+        @Override
+        public IOBuffer getOut() {
+            return netOut;
+        }
+    } 
+ 
+    // TODO: in-process communication without sockets for testing
+    ConnectedCallback acceptor;
+    MemoryIOConnector server;
+    
+    public MemoryIOConnector withServer(MemoryIOConnector server) {
+        this.server = server;
+        return server;
+    }
+    
+    @Override
+    public void acceptor(ConnectedCallback sc, CharSequence port, Object extra) 
+            throws IOException {
+        this.acceptor = sc;
+    }
+
+    @Override
+    public void connect(String host, int port, ConnectedCallback sc)
+            throws IOException {
+        IOChannel ch = new MemoryIOChannel();
+        IOChannel sch = new MemoryIOChannel();
+        // TODO: mix
+        if (server != null && server.acceptor != null) {
+            server.acceptor.handleConnected(sch);
+        }
+        sc.handleConnected(ch);
+    }
+    
+}
\ No newline at end of file

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java?rev=884410&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java Thu Nov 26 06:35:43 2009
@@ -0,0 +1,200 @@
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectionKey;
+
+
+/** 
+ * Wrapper around the real channel, with selector-specific info.
+ * 
+ * It is stored as an attachment in the selector.
+ */
+public class NioChannel implements ByteChannel {
+    
+    public static interface NioChannelCallback {
+        public void handleConnected(NioChannel ch) throws IOException;
+        public void handleClosed(NioChannel ch) throws IOException;
+        public void handleReadable(NioChannel ch) throws IOException;
+        public void handleWriteable(NioChannel ch) throws IOException;
+        
+    }
+    
+    NioChannel(NioThread sel) {
+        this.sel = sel;
+    }
+
+    // APR long is wrapped in a ByteChannel as well - with few other longs.
+    Channel channel;
+    
+    // sync access.
+    Object selKey;
+
+    NioThread sel;
+    
+    /** 
+     * If != 0 - the callback will be notified closely after this time.
+     * Used for timeouts. 
+     */
+    long nextTimeEvent = 0;
+    
+    // Callbacks
+    Runnable timeEvent;
+    
+    NioChannelCallback callback;
+
+    
+    Throwable lastException;
+
+    // True if the callback wants to be notified of read/write
+    boolean writeInterest;
+    boolean readInterest;
+
+    // shutdownOutput has been called ?
+    private boolean outClosed = false;
+    
+    // read() returned -1 OR input buffer closed ( no longer interested )
+    boolean inClosed = false;
+    
+    // Saved to allow debug messages for bad interest/looping
+    int lastReadResult;
+    int zeroReads = 0;
+    int lastWriteResult;
+    
+    protected NioChannel() {
+        
+    }
+    
+    public NioThread getSelectorThread() {
+        return sel;
+    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("SelData/")
+        .append(writeInterest ? "W/" : "")
+        .append(readInterest ? "R/" : "")
+        .append(outClosed ? "Out-CLOSE/" : "")
+        .append(inClosed ? "In-CLOSE/" : "")
+        .append(selKey == null ? -1 : ((SelectionKey) (selKey)).interestOps())
+        .append("/")
+        .append(channel.toString());
+        
+        return sb.toString();
+    }
+    
+    public Channel getChannel() {
+        return channel;
+    }
+    
+    public boolean isOpen() {
+        // in and out open
+        return channel.isOpen() && !outClosed && !inClosed;
+    }
+    
+    public int read(ByteBuffer bb) throws IOException {
+        return sel.readNonBlocking(this, bb);
+    }
+
+    public int write(ByteBuffer bb) throws IOException {
+        return sel.writeNonBlocking(this, bb);
+    }
+    
+    public void readInterest(boolean b) throws IOException {
+        sel.readInterest(this, b);
+    }
+
+    public void writeInterest() throws IOException {
+        sel.writeInterest(this);
+    }
+ 
+    public InetAddress getAddress(boolean remote) {
+        return sel.getAddress(this, remote);
+    }
+
+    public int getPort(boolean remote) {
+        return sel.getPort(this, remote);
+    }
+    
+    /**
+     * Run in selector thread.
+     */
+    public void runInSelectorThread(Runnable t) throws IOException {
+        sel.runInSelectorThread(t);
+    }
+
+    /**
+     * Request a timer event. The thread will generate the events at 
+     * a configurable interval - for example no more often than 0.5 sec.
+     */
+    public void setTimer(long timeMs, Runnable cb) {
+        this.nextTimeEvent = timeMs;
+        this.timeEvent = cb;
+    }
+    
+    /**
+     *  shutdown out + in
+     *  If there is still data in the input buffer - RST will be sent
+     *  instead of FIN.
+     *  
+     * 
+     * The proper way to close a connection is to shutdownOutput() first, 
+     * wait until read() return -1, then call close().
+     * 
+     * If read() returns -1, you need to finish sending, call shutdownOutput()
+     * than close. 
+     * If read() returns -1 and there is an error - call close() 
+     * directly. 
+     * 
+     */
+    @Override
+    public void close() throws IOException {
+        shutdownOutput();
+        sel.close(this, null);
+    }
+
+    /**
+     *  Send TCP close(FIN). HTTP uses this to transmit end of body. The other end
+     *  detects this with a '-1' in read().
+     *  
+     *  All other forms of close() are reported as exceptions in read().
+     * 
+     * @throws IOException
+     */
+    public void shutdownOutput() throws IOException {
+        synchronized (channel) {
+            if (!outClosed) {
+                outClosed = true;
+                try {
+                    sel.shutdownOutput(this);
+                } catch (IOException ex) {
+                    // ignore
+                }
+            } 
+            if (inClosed) {
+                sel.close(this, null);
+            }
+        }
+    }
+    
+    void inputClosed() throws IOException {
+        synchronized (channel) {
+            if (inClosed) {
+                // already closed
+                return;
+            }
+            inClosed = true; // detected end
+            if (outClosed) {
+                sel.close(this, null);
+            } else {
+                // Don't close the channel - write may still work ?
+                readInterest(false);
+            }
+        }
+    }
+    
+    boolean closeCalled = false;
+}
\ No newline at end of file

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org