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