You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2012/02/02 21:00:44 UTC
svn commit: r1239799 [8/9] - in
/myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared:
application/ config/ context/flash/ renderkit/ renderkit/html/
renderkit/html/util/ resource/ util/ util/io/ util/xml/
Added: myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StreamCharBuffer.java
URL: http://svn.apache.org/viewvc/myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StreamCharBuffer.java?rev=1239799&view=auto
==============================================================================
--- myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StreamCharBuffer.java (added)
+++ myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StreamCharBuffer.java Thu Feb 2 20:00:42 2012
@@ -0,0 +1,2306 @@
+/*
+ * 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.myfaces.shared.util;
+
+import java.io.EOFException;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>
+ * StreamCharBuffer is a multipurpose in-memory buffer that can replace JDK
+ * in-memory buffers (StringBuffer, StringBuilder, StringWriter).
+ * </p>
+ *
+ * <p>
+ * Grails GSP rendering uses this class as a buffer that is optimized for performance.
+ * </p>
+ *
+ * <p>
+ * StreamCharBuffer keeps the buffer in a linked list of "chunks". The main
+ * difference compared to JDK in-memory buffers (StringBuffer, StringBuilder &
+ * StringWriter) is that the buffer can be held in several smaller buffers
+ * ("chunks" here). In JDK in-memory buffers, the buffer has to be expanded
+ * whenever it gets filled up. The old buffer's data is copied to the new one
+ * and the old one is discarded. In StreamCharBuffer, there are several ways to
+ * prevent unnecessary allocation & copy operations. The StreamCharBuffer
+ * contains a linked list of different type of chunks: char arrays,
+ * java.lang.String chunks and other StreamCharBuffers as sub chunks. A
+ * StringChunk is appended to the linked list whenever a java.lang.String of a
+ * length that exceeds the "stringChunkMinSize" value is written to the buffer.
+ * </p>
+ *
+ * <p>
+ * Grails tag libraries also use a StreamCharBuffer to "capture" the output of
+ * the taglib and return it to the caller. The buffer can be appended to it's
+ * parent buffer directly without extra object generation (like converting to
+ * java.lang.String in between).
+ *
+ * for example this line of code in a taglib would just append the buffer
+ * returned from the body closure evaluation to the buffer of the taglib:<br>
+ * <code>
+ * out << body()
+ * </code><br>
+ * other example:<br>
+ * <code>
+ * out << g.render(template: '/some/template', model:[somebean: somebean])
+ * </code><br>
+ * There's no extra java.lang.String generation overhead.
+ *
+ * </p>
+ *
+ * <p>
+ * There's a java.io.Writer interface for appending character data to the buffer
+ * and a java.io.Reader interface for reading data.
+ * </p>
+ *
+ * <p>
+ * Each {@link #getReader()} call will create a new reader instance that keeps
+ * it own state.<br>
+ * There is a alternative method {@link #getReader(boolean)} for creating the
+ * reader. When reader is created by calling getReader(true), the reader will
+ * remove already read characters from the buffer. In this mode only a single
+ * reader instance is supported.
+ * </p>
+ *
+ * <p>
+ * There's also several other options for reading data:<br>
+ * {@link #readAsCharArray()} reads the buffer to a char[] array<br>
+ * {@link #readAsString()} reads the buffer and wraps the char[] data as a
+ * String<br>
+ * {@link #writeTo(Writer)} writes the buffer to a java.io.Writer<br>
+ * {@link #toCharArray()} returns the buffer as a char[] array, caches the
+ * return value internally so that this method can be called several times.<br>
+ * {@link #toString()} returns the buffer as a String, caches the return value
+ * internally<br>
+ * </p>
+ *
+ * <p>
+ * By using the "connectTo" method, one can connect the buffer directly to a
+ * target java.io.Writer. The internal buffer gets flushed automaticly to the
+ * target whenever the buffer gets filled up. See connectTo(Writer).
+ * </p>
+ *
+ * <p>
+ * <b>This class is not thread-safe.</b> Object instances of this class are
+ * intended to be used by a single Thread. The Reader and Writer interfaces can
+ * be open simultaneous and the same Thread can write and read several times.
+ * </p>
+ *
+ * <p>
+ * Main operation principle:<br>
+ * </p>
+ * <p>
+ * StreamCharBuffer keeps the buffer in a linked link of "chunks".<br>
+ * The main difference compared to JDK in-memory buffers (StringBuffer,
+ * StringBuilder & StringWriter) is that the buffer can be held in several
+ * smaller buffers ("chunks" here).<br>
+ * In JDK in-memory buffers, the buffer has to be expanded whenever it gets
+ * filled up. The old buffer's data is copied to the new one and the old one is
+ * discarded.<br>
+ * In StreamCharBuffer, there are several ways to prevent unnecessary allocation
+ * & copy operations.
+ * </p>
+ * <p>
+ * There can be several different type of chunks: char arrays (
+ * {@code CharBufferChunk}), String chunks ({@code StringChunk}) and other
+ * StreamCharBuffers as sub chunks ({@code StreamCharBufferSubChunk}).
+ * </p>
+ * <p>
+ * Child StreamCharBuffers can be changed after adding to parent buffer. The
+ * flush() method must be called on the child buffer's Writer to notify the
+ * parent that the child buffer's content has been changed (used for calculating
+ * total size).
+ * </p>
+ * <p>
+ * A StringChunk is appended to the linked list whenever a java.lang.String of a
+ * length that exceeds the "stringChunkMinSize" value is written to the buffer.
+ * </p>
+ * <p>
+ * If the buffer is in "connectTo" mode, any String or char[] that's length is
+ * over writeDirectlyToConnectedMinSize gets written directly to the target. The
+ * buffer content will get fully flushed to the target before writing the String
+ * or char[].
+ * </p>
+ * <p>
+ * There can be several targets "listening" the buffer in "connectTo" mode. The
+ * same content will be written to all targets.
+ * <p>
+ * <p>
+ * Growable chunksize: By default, a newly allocated chunk's size will grow
+ * based on the total size of all written chunks.<br>
+ * The default growProcent value is 100. If the total size is currently 1024,
+ * the newly created chunk will have a internal buffer that's size is 1024.<br>
+ * Growable chunksize can be turned off by setting the growProcent to 0.<br>
+ * There's a default maximum chunksize of 1MB by default. The minimum size is
+ * the initial chunksize size.<br>
+ * </p>
+ *
+ * <p>
+ * System properties to change default configuration parameters:<br>
+ * <table>
+ * <tr>
+ * <th>System Property name</th>
+ * <th>Description</th>
+ * <th>Default value</th>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.chunksize</td>
+ * <td>default chunk size - the size the first allocated buffer</td>
+ * <td>512</td>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.maxchunksize</td>
+ * <td>maximum chunk size - the maximum size of the allocated buffer</td>
+ * <td>1048576</td>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.growprocent</td>
+ * <td>growing buffer percentage - the newly allocated buffer is defined by
+ * total_size * (growpercent/100)</td>
+ * <td>100</td>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.subbufferchunkminsize</td>
+ * <td>minimum size of child StreamCharBuffer chunk - if the size is smaller,
+ * the content is copied to the parent buffer</td>
+ * <td>512</td>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.substringchunkminsize</td>
+ * <td>minimum size of String chunks - if the size is smaller, the content is
+ * copied to the buffer</td>
+ * <td>512</td>
+ * </tr>
+ * <tr>
+ * <td>streamcharbuffer.chunkminsize</td>
+ * <td>minimum size of chunk that gets written directly to the target in
+ * connected mode.</td>
+ * <td>256</td>
+ * </tr>
+ * </table>
+ *
+ * Configuration values can also be changed for each instance of
+ * StreamCharBuffer individually. Default values are defined with System
+ * Properties.
+ *
+ * </p>
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ * @see org.codehaus.groovy.grails.web.util.StreamCharBuffer
+ * file licensed under ASL v2.0
+ * Copyright 2009 the original author or authors.
+ */
+public class StreamCharBuffer implements /*Writable,*/CharSequence,
+ Externalizable
+{
+ static final long serialVersionUID = 5486972234419632945L;
+ //private static final Log log=LogFactory.getLog(StreamCharBuffer.class);
+
+ private static final int DEFAULT_CHUNK_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.chunksize", 512);
+ private static final int DEFAULT_MAX_CHUNK_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.maxchunksize", 1024 * 1024);
+ private static final int DEFAULT_CHUNK_SIZE_GROW_PROCENT = Integer
+ .getInteger("oam.streamcharbuffer.growprocent", 100);
+ private static final int SUB_BUFFERCHUNK_MIN_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.subbufferchunkminsize", 512);
+ private static final int SUB_STRINGCHUNK_MIN_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.substringchunkminsize", 512);
+ private static final int WRITE_DIRECT_MIN_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.writedirectminsize", 1024);
+ private static final int CHUNK_MIN_SIZE = Integer.getInteger(
+ "oam.streamcharbuffer.chunkminsize", 256);
+
+ private final int firstChunkSize;
+ private final int growProcent;
+ private final int maxChunkSize;
+ private int subStringChunkMinSize = SUB_STRINGCHUNK_MIN_SIZE;
+ private int subBufferChunkMinSize = SUB_BUFFERCHUNK_MIN_SIZE;
+ private int writeDirectlyToConnectedMinSize = WRITE_DIRECT_MIN_SIZE;
+ private int chunkMinSize = CHUNK_MIN_SIZE;
+
+ private int chunkSize;
+ private int totalChunkSize;
+
+ private final StreamCharBufferWriter writer;
+ private List<ConnectedWriter> connectedWriters;
+ private Writer connectedWritersWriter;
+
+ boolean preferSubChunkWhenWritingToOtherBuffer = false;
+
+ private AllocatedBuffer allocBuffer;
+ private AbstractChunk firstChunk;
+ private AbstractChunk lastChunk;
+ private int totalCharsInList;
+ private int totalCharsInDynamicChunks;
+ private int sizeAtLeast;
+ private StreamCharBufferKey bufferKey = new StreamCharBufferKey();
+ private Map<StreamCharBufferKey, StreamCharBufferSubChunk> dynamicChunkMap;
+
+ private Set<SoftReference<StreamCharBufferKey>> parentBuffers;
+ int allocatedBufferIdSequence = 0;
+ int readerCount = 0;
+ boolean hasReaders = false;
+
+ public StreamCharBuffer()
+ {
+ this(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE_GROW_PROCENT,
+ DEFAULT_MAX_CHUNK_SIZE);
+ }
+
+ public StreamCharBuffer(int chunkSize)
+ {
+ this(chunkSize, DEFAULT_CHUNK_SIZE_GROW_PROCENT, DEFAULT_MAX_CHUNK_SIZE);
+ }
+
+ public StreamCharBuffer(int chunkSize, int growProcent)
+ {
+ this(chunkSize, growProcent, DEFAULT_MAX_CHUNK_SIZE);
+ }
+
+ public StreamCharBuffer(int chunkSize, int growProcent, int maxChunkSize)
+ {
+ this.firstChunkSize = chunkSize;
+ this.growProcent = growProcent;
+ this.maxChunkSize = maxChunkSize;
+ writer = new StreamCharBufferWriter();
+ reset(true);
+ }
+
+ private class StreamCharBufferKey
+ {
+ StreamCharBuffer getBuffer()
+ {
+ return StreamCharBuffer.this;
+ }
+ }
+
+ public boolean isPreferSubChunkWhenWritingToOtherBuffer()
+ {
+ return preferSubChunkWhenWritingToOtherBuffer;
+ }
+
+ public void setPreferSubChunkWhenWritingToOtherBuffer(
+ boolean preferSubChunkWhenWritingToOtherBuffer)
+ {
+ this.preferSubChunkWhenWritingToOtherBuffer = preferSubChunkWhenWritingToOtherBuffer;
+ }
+
+ public final void reset()
+ {
+ reset(true);
+ }
+
+ /**
+ * resets the state of this buffer (empties it)
+ *
+ * @param resetChunkSize
+ */
+ public final void reset(boolean resetChunkSize)
+ {
+ firstChunk = null;
+ lastChunk = null;
+ totalCharsInList = 0;
+ totalCharsInDynamicChunks = -1;
+ sizeAtLeast = -1;
+ if (resetChunkSize)
+ {
+ chunkSize = firstChunkSize;
+ totalChunkSize = 0;
+ }
+ allocBuffer = new AllocatedBuffer(chunkSize);
+ dynamicChunkMap = new HashMap<StreamCharBufferKey, StreamCharBufferSubChunk>();
+ }
+
+ /**
+ * Clears the buffer and notifies the parents of this buffer of the change
+ *
+ */
+ public final void clear()
+ {
+ reset();
+ notifyBufferChange();
+ }
+
+ /**
+ * Connect this buffer to a target Writer.
+ *
+ * When the buffer (a chunk) get filled up, it will automaticly write it's content to the Writer
+ *
+ * @param w
+ */
+ public final void connectTo(Writer w)
+ {
+ connectTo(w, true);
+ }
+
+ public final void connectTo(Writer w, boolean autoFlush)
+ {
+ initConnected();
+ connectedWriters.add(new ConnectedWriter(w, autoFlush));
+ initConnectedWritersWriter();
+ }
+
+ private void initConnectedWritersWriter()
+ {
+ if (connectedWriters.size() > 1)
+ {
+ connectedWritersWriter = new MultiOutputWriter(connectedWriters);
+ }
+ else
+ {
+ connectedWritersWriter = new SingleOutputWriter(
+ connectedWriters.get(0));
+ }
+ }
+
+ public final void connectTo(LazyInitializingWriter w)
+ {
+ connectTo(w, true);
+ }
+
+ public final void connectTo(LazyInitializingWriter w, boolean autoFlush)
+ {
+ initConnected();
+ connectedWriters.add(new ConnectedWriter(w, autoFlush));
+ initConnectedWritersWriter();
+ }
+
+ public final void removeConnections()
+ {
+ if (connectedWriters != null)
+ {
+ connectedWriters.clear();
+ connectedWritersWriter = null;
+ }
+ }
+
+ private void initConnected()
+ {
+ if (connectedWriters == null)
+ {
+ connectedWriters = new ArrayList<ConnectedWriter>(2);
+ }
+ }
+
+ public int getSubStringChunkMinSize()
+ {
+ return subStringChunkMinSize;
+ }
+
+ /**
+ * Minimum size for a String to be added as a StringChunk instead of copying content to
+ * the char[] buffer of the current StreamCharBufferChunk
+ *
+ * @param stringChunkMinSize
+ */
+ public void setSubStringChunkMinSize(int stringChunkMinSize)
+ {
+ this.subStringChunkMinSize = stringChunkMinSize;
+ }
+
+ public int getSubBufferChunkMinSize()
+ {
+ return subBufferChunkMinSize;
+ }
+
+ public void setSubBufferChunkMinSize(int subBufferChunkMinSize)
+ {
+ this.subBufferChunkMinSize = subBufferChunkMinSize;
+ }
+
+ public int getWriteDirectlyToConnectedMinSize()
+ {
+ return writeDirectlyToConnectedMinSize;
+ }
+
+ /**
+ * Minimum size for a String or char[] to get written directly to connected writer (in "connectTo" mode).
+ *
+ * @param writeDirectlyToConnectedMinSize
+ */
+ public void setWriteDirectlyToConnectedMinSize(
+ int writeDirectlyToConnectedMinSize)
+ {
+ this.writeDirectlyToConnectedMinSize = writeDirectlyToConnectedMinSize;
+ }
+
+ public int getChunkMinSize()
+ {
+ return chunkMinSize;
+ }
+
+ public void setChunkMinSize(int chunkMinSize)
+ {
+ this.chunkMinSize = chunkMinSize;
+ }
+
+ /**
+ * Writer interface for adding/writing data to the buffer.
+ *
+ * @return the Writer
+ */
+ public Writer getWriter()
+ {
+ return writer;
+ }
+
+ /**
+ * Creates a new Reader instance for reading/consuming data from the buffer.
+ * Each call creates a new instance that will keep it's reading state. There can be several readers on
+ * the buffer. (single thread only supported)
+ *
+ * @return the Reader
+ */
+ public Reader getReader()
+ {
+ return getReader(false);
+ }
+
+ /**
+ * Like getReader(), but when removeAfterReading is true, the read data will be removed from the buffer.
+ *
+ * @param removeAfterReading
+ * @return the Reader
+ */
+ public Reader getReader(boolean removeAfterReading)
+ {
+ readerCount++;
+ hasReaders = true;
+ return new StreamCharBufferReader(removeAfterReading);
+ }
+
+ /**
+ * Writes the buffer content to a target java.io.Writer
+ *
+ * @param target
+ * @throws IOException
+ */
+ public Writer writeTo(Writer target) throws IOException
+ {
+ writeTo(target, false, false);
+ return getWriter();
+ }
+
+ /**
+ * Writes the buffer content to a target java.io.Writer
+ *
+ * @param target Writer
+ * @param flushTarget calls target.flush() before finishing
+ * @param emptyAfter empties the buffer if true
+ * @throws IOException
+ */
+ public void writeTo(Writer target, boolean flushTarget, boolean emptyAfter)
+ throws IOException
+ {
+ //if (target instanceof GrailsWrappedWriter) {
+ // target = ((GrailsWrappedWriter)target).unwrap();
+ //}
+ if (target instanceof StreamCharBufferWriter)
+ {
+ if (target == writer)
+ {
+ throw new IllegalArgumentException(
+ "Cannot write buffer to itself.");
+ }
+ ((StreamCharBufferWriter) target).write(this);
+ return;
+ }
+ writeToImpl(target, flushTarget, emptyAfter);
+ }
+
+ private void writeToImpl(Writer target, boolean flushTarget,
+ boolean emptyAfter) throws IOException
+ {
+ AbstractChunk current = firstChunk;
+ while (current != null)
+ {
+ current.writeTo(target);
+ current = current.next;
+ }
+ if (emptyAfter)
+ {
+ firstChunk = null;
+ lastChunk = null;
+ totalCharsInList = 0;
+ totalCharsInDynamicChunks = -1;
+ sizeAtLeast = -1;
+ dynamicChunkMap.clear();
+ }
+ allocBuffer.writeTo(target);
+ if (emptyAfter)
+ {
+ allocBuffer.reuseBuffer();
+ }
+ if (flushTarget)
+ {
+ target.flush();
+ }
+ }
+
+ /**
+ * Reads the buffer to a char[].
+ *
+ * @return the chars
+ */
+ public char[] readAsCharArray()
+ {
+ int currentSize = size();
+ if (currentSize == 0)
+ {
+ return new char[0];
+ }
+
+ FixedCharArrayWriter target = new FixedCharArrayWriter(currentSize);
+ try
+ {
+ writeTo(target);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unexpected IOException", e);
+ }
+ return target.getCharArray();
+ }
+
+ /**
+ * Reads the buffer to a String.
+ *
+ * @return the String
+ */
+ public String readAsString()
+ {
+ char[] buf = readAsCharArray();
+ if (buf.length > 0)
+ {
+ return StringCharArrayAccessor.createString(buf);
+ }
+
+ return "";
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Reads (and empties) the buffer to a String, but caches the return value for subsequent calls.
+ * If more content has been added between 2 calls, the returned value will be joined from the previously
+ * cached value and the data read from the buffer.
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ if (firstChunk == lastChunk && firstChunk instanceof StringChunk
+ && allocBuffer.charsUsed() == 0
+ && ((StringChunk) firstChunk).isSingleBuffer())
+ {
+ return ((StringChunk) firstChunk).str;
+ }
+
+ int initialReaderCount = readerCount;
+ String str = readAsString();
+ if (initialReaderCount == 0)
+ {
+ // if there are no readers, the result can be cached
+ reset();
+ if (str.length() > 0)
+ {
+ addChunk(new StringChunk(str, 0, str.length()));
+ }
+ }
+ return str;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses String's hashCode to support compatibility with String instances in maps, sets, etc.
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * equals uses String.equals to check for equality to support compatibility with String instances
+ * in maps, sets, etc.
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this)
+ {
+ return true;
+ }
+
+ if (!(o instanceof CharSequence))
+ {
+ return false;
+ }
+
+ CharSequence other = (CharSequence) o;
+
+ return toString().equals(other.toString());
+ }
+
+ public String plus(String value)
+ {
+ return toString() + value;
+ }
+
+ public String plus(Object value)
+ {
+ return toString() + String.valueOf(value);
+ }
+
+ /**
+ * Reads the buffer to a char[].
+ *
+ * Caches the result if there aren't any readers.
+ *
+ * @return the chars
+ */
+ public char[] toCharArray()
+ {
+ // check if there is a cached single charbuffer
+ if (firstChunk == lastChunk && firstChunk instanceof CharBufferChunk
+ && allocBuffer.charsUsed() == 0
+ && ((CharBufferChunk) firstChunk).isSingleBuffer())
+ {
+ return ((CharBufferChunk) firstChunk).buffer;
+ }
+
+ int initialReaderCount = readerCount;
+ char[] buf = readAsCharArray();
+ if (initialReaderCount == 0)
+ {
+ // if there are no readers, the result can be cached
+ reset();
+ if (buf.length > 0)
+ {
+ addChunk(new CharBufferChunk(-1, buf, 0, buf.length));
+ }
+ }
+ return buf;
+ }
+
+ public int size()
+ {
+ int total = totalCharsInList;
+ if (totalCharsInDynamicChunks == -1)
+ {
+ totalCharsInDynamicChunks = 0;
+ for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
+ {
+ totalCharsInDynamicChunks += chunk.size();
+ }
+ }
+ total += totalCharsInDynamicChunks;
+ total += allocBuffer.charsUsed();
+ sizeAtLeast = total;
+ return total;
+ }
+
+ public boolean isEmpty()
+ {
+ return !isNotEmpty();
+ }
+
+ boolean isNotEmpty()
+ {
+ if (totalCharsInList > 0)
+ {
+ return true;
+ }
+ if (totalCharsInDynamicChunks > 0)
+ {
+ return true;
+ }
+ if (allocBuffer.charsUsed() > 0)
+ {
+ return true;
+ }
+ if (totalCharsInDynamicChunks == -1)
+ {
+ for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
+ {
+ if (chunk.getSubBuffer().isNotEmpty())
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean isSizeLarger(int minSize)
+ {
+ if (minSize <= sizeAtLeast)
+ {
+ return true;
+ }
+
+ boolean retval = calculateIsSizeLarger(minSize);
+ if (retval && minSize > sizeAtLeast)
+ {
+ sizeAtLeast = minSize;
+ }
+ return retval;
+ }
+
+ private boolean calculateIsSizeLarger(int minSize)
+ {
+ int total = totalCharsInList;
+ total += allocBuffer.charsUsed();
+ if (total > minSize)
+ {
+ return true;
+ }
+ if (totalCharsInDynamicChunks != -1)
+ {
+ total += totalCharsInDynamicChunks;
+ if (total > minSize)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ for (StreamCharBufferSubChunk chunk : dynamicChunkMap.values())
+ {
+ if (!chunk.hasCachedSize()
+ && chunk.getSubBuffer().isSizeLarger(minSize - total))
+ {
+ return true;
+ }
+ total += chunk.size();
+ if (total > minSize)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ int allocateSpace() throws IOException
+ {
+ int spaceLeft = allocBuffer.spaceLeft();
+ if (spaceLeft == 0)
+ {
+ spaceLeft = appendCharBufferChunk(true);
+ }
+ return spaceLeft;
+ }
+
+ private int appendCharBufferChunk(boolean flushInConnected)
+ throws IOException
+ {
+ int spaceLeft = 0;
+ if (flushInConnected && isConnectedMode())
+ {
+ flushToConnected();
+ if (!isChunkSizeResizeable())
+ {
+ allocBuffer.reuseBuffer();
+ spaceLeft = allocBuffer.spaceLeft();
+ }
+ else
+ {
+ spaceLeft = 0;
+ }
+ }
+ else
+ {
+ if (allocBuffer.hasChunk())
+ {
+ addChunk(allocBuffer.createChunk());
+ }
+ spaceLeft = allocBuffer.spaceLeft();
+ }
+ if (spaceLeft == 0)
+ {
+ totalChunkSize += allocBuffer.chunkSize();
+ resizeChunkSizeAsProcentageOfTotalSize();
+ allocBuffer = new AllocatedBuffer(chunkSize);
+ spaceLeft = allocBuffer.spaceLeft();
+ }
+ return spaceLeft;
+ }
+
+ void appendStringChunk(String str, int off, int len) throws IOException
+ {
+ appendCharBufferChunk(false);
+ addChunk(new StringChunk(str, off, len));
+ }
+
+ public void appendStreamCharBufferChunk(StreamCharBuffer subBuffer)
+ throws IOException
+ {
+ appendCharBufferChunk(false);
+ addChunk(new StreamCharBufferSubChunk(subBuffer));
+ }
+
+ void addChunk(AbstractChunk newChunk)
+ {
+ if (lastChunk != null)
+ {
+ lastChunk.next = newChunk;
+ if (hasReaders)
+ {
+ // double link only if there are active readers since backwards iterating is only required
+ //for simultaneous writer & reader
+ newChunk.prev = lastChunk;
+ }
+ }
+ lastChunk = newChunk;
+ if (firstChunk == null)
+ {
+ firstChunk = newChunk;
+ }
+ if (newChunk instanceof StreamCharBufferSubChunk)
+ {
+ StreamCharBufferSubChunk bufSubChunk = (StreamCharBufferSubChunk) newChunk;
+ dynamicChunkMap.put(bufSubChunk.streamCharBuffer.bufferKey,
+ bufSubChunk);
+ }
+ else
+ {
+ totalCharsInList += newChunk.size();
+ }
+ }
+
+ public boolean isConnectedMode()
+ {
+ return connectedWriters != null && !connectedWriters.isEmpty();
+ }
+
+ private void flushToConnected() throws IOException
+ {
+ writeTo(connectedWritersWriter, true, true);
+ }
+
+ protected boolean isChunkSizeResizeable()
+ {
+ return (growProcent > 0);
+ }
+
+ protected void resizeChunkSizeAsProcentageOfTotalSize()
+ {
+ if (growProcent == 0)
+ {
+ return;
+ }
+
+ if (growProcent == 100)
+ {
+ chunkSize = Math.min(totalChunkSize, maxChunkSize);
+ }
+ else if (growProcent == 200)
+ {
+ chunkSize = Math.min(totalChunkSize << 1, maxChunkSize);
+ }
+ else if (growProcent > 0)
+ {
+ chunkSize = Math.max(Math.min((totalChunkSize * growProcent) / 100,
+ maxChunkSize), firstChunkSize);
+ }
+ }
+
+ protected static final void arrayCopy(char[] src, int srcPos, char[] dest,
+ int destPos, int length)
+ {
+ if (length == 1)
+ {
+ dest[destPos] = src[srcPos];
+ }
+ else
+ {
+ System.arraycopy(src, srcPos, dest, destPos, length);
+ }
+ }
+
+ /**
+ * This is the java.io.Writer implementation for StreamCharBuffer
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ */
+ public final class StreamCharBufferWriter extends Writer
+ {
+ boolean closed = false;
+ int writerUsedCounter = 0;
+ boolean increaseCounter = true;
+
+ @Override
+ public final void write(final char[] b, final int off, final int len)
+ throws IOException
+ {
+ if (b == null)
+ {
+ throw new NullPointerException();
+ }
+
+ if ((off < 0) || (off > b.length) || (len < 0)
+ || ((off + len) > b.length) || ((off + len) < 0))
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (len == 0)
+ {
+ return;
+ }
+
+ markUsed();
+ if (shouldWriteDirectly(len))
+ {
+ appendCharBufferChunk(true);
+ connectedWritersWriter.write(b, off, len);
+ }
+ else
+ {
+ int charsLeft = len;
+ int currentOffset = off;
+ while (charsLeft > 0)
+ {
+ int spaceLeft = allocateSpace();
+ int writeChars = Math.min(spaceLeft, charsLeft);
+ allocBuffer.write(b, currentOffset, writeChars);
+ charsLeft -= writeChars;
+ currentOffset += writeChars;
+ }
+ }
+ }
+
+ private final boolean shouldWriteDirectly(final int len)
+ {
+ if (!isConnectedMode())
+ {
+ return false;
+ }
+
+ if (!(writeDirectlyToConnectedMinSize >= 0 && len >= writeDirectlyToConnectedMinSize))
+ {
+ return false;
+ }
+
+ return isNextChunkBigEnough(len);
+ }
+
+ private final boolean isNextChunkBigEnough(final int len)
+ {
+ return (len > getNewChunkMinSize());
+ }
+
+ private final int getDirectChunkMinSize()
+ {
+ if (!isConnectedMode())
+ {
+ return -1;
+ }
+ if (writeDirectlyToConnectedMinSize >= 0)
+ {
+ return writeDirectlyToConnectedMinSize;
+ }
+
+ return getNewChunkMinSize();
+ }
+
+ private final int getNewChunkMinSize()
+ {
+ if (chunkMinSize <= 0 || allocBuffer.charsUsed() == 0
+ || allocBuffer.charsUsed() >= chunkMinSize)
+ {
+ return 0;
+ }
+ return allocBuffer.spaceLeft();
+ }
+
+ @Override
+ public final void write(final String str) throws IOException
+ {
+ write(str, 0, str.length());
+ }
+
+ @Override
+ public final void write(final String str, final int off, final int len)
+ throws IOException
+ {
+ if (len == 0)
+ {
+ return;
+ }
+ markUsed();
+ if (shouldWriteDirectly(len))
+ {
+ appendCharBufferChunk(true);
+ connectedWritersWriter.write(str, off, len);
+ }
+ else if (len >= subStringChunkMinSize && isNextChunkBigEnough(len))
+ {
+ appendStringChunk(str, off, len);
+ }
+ else
+ {
+ int charsLeft = len;
+ int currentOffset = off;
+ while (charsLeft > 0)
+ {
+ int spaceLeft = allocateSpace();
+ int writeChars = Math.min(spaceLeft, charsLeft);
+ allocBuffer.writeString(str, currentOffset, writeChars);
+ charsLeft -= writeChars;
+ currentOffset += writeChars;
+ }
+ }
+ }
+
+ public final void write(StreamCharBuffer subBuffer) throws IOException
+ {
+ markUsed();
+ int directChunkMinSize = getDirectChunkMinSize();
+ if (directChunkMinSize != -1
+ && subBuffer.isSizeLarger(directChunkMinSize))
+ {
+ appendCharBufferChunk(true);
+ subBuffer.writeToImpl(connectedWritersWriter, false, false);
+ }
+ else if (subBuffer.preferSubChunkWhenWritingToOtherBuffer
+ || subBuffer.isSizeLarger(Math.max(subBufferChunkMinSize,
+ getNewChunkMinSize())))
+ {
+ if (subBuffer.preferSubChunkWhenWritingToOtherBuffer)
+ {
+ StreamCharBuffer.this.preferSubChunkWhenWritingToOtherBuffer = true;
+ }
+ appendStreamCharBufferChunk(subBuffer);
+ subBuffer.addParentBuffer(StreamCharBuffer.this);
+ }
+ else
+ {
+ subBuffer.writeToImpl(this, false, false);
+ }
+ }
+
+ @Override
+ public final Writer append(final CharSequence csq, final int start,
+ final int end) throws IOException
+ {
+ markUsed();
+ if (csq == null)
+ {
+ write("null");
+ }
+ else
+ {
+ if (csq instanceof String || csq instanceof StringBuffer
+ || csq instanceof StringBuilder)
+ {
+ int len = end - start;
+ int charsLeft = len;
+ int currentOffset = start;
+ while (charsLeft > 0)
+ {
+ int spaceLeft = allocateSpace();
+ int writeChars = Math.min(spaceLeft, charsLeft);
+ if (csq instanceof String)
+ {
+ allocBuffer.writeString((String) csq,
+ currentOffset, writeChars);
+ }
+ else if (csq instanceof StringBuffer)
+ {
+ allocBuffer.writeStringBuffer((StringBuffer) csq,
+ currentOffset, writeChars);
+ }
+ else if (csq instanceof StringBuilder)
+ {
+ allocBuffer.writeStringBuilder((StringBuilder) csq,
+ currentOffset, writeChars);
+ }
+ charsLeft -= writeChars;
+ currentOffset += writeChars;
+ }
+ }
+ else
+ {
+ write(csq.subSequence(start, end).toString());
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public final Writer append(final CharSequence csq) throws IOException
+ {
+ markUsed();
+ if (csq == null)
+ {
+ write("null");
+ }
+ else
+ {
+ append(csq, 0, csq.length());
+
+ }
+ return this;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ closed = true;
+ flush();
+ }
+
+ public boolean isClosed()
+ {
+ return closed;
+ }
+
+ public boolean isUsed()
+ {
+ return writerUsedCounter > 0;
+ }
+
+ public final void markUsed()
+ {
+ if (increaseCounter)
+ {
+ writerUsedCounter++;
+ if (!hasReaders)
+ {
+ increaseCounter = false;
+ }
+ }
+ }
+
+ public int resetUsed()
+ {
+ int prevUsed = writerUsedCounter;
+ writerUsedCounter = 0;
+ increaseCounter = true;
+ return prevUsed;
+ }
+
+ @Override
+ public void write(final int b) throws IOException
+ {
+ markUsed();
+ allocateSpace();
+ allocBuffer.write((char) b);
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ if (isConnectedMode())
+ {
+ flushToConnected();
+ }
+ notifyBufferChange();
+ }
+
+ public final StreamCharBuffer getBuffer()
+ {
+ return StreamCharBuffer.this;
+ }
+ }
+
+ /**
+ * This is the java.io.Reader implementation for StreamCharBuffer
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ */
+
+ final public class StreamCharBufferReader extends Reader
+ {
+ boolean eofException = false;
+ int eofReachedCounter = 0;
+ ChunkReader chunkReader;
+ ChunkReader lastChunkReader;
+ boolean removeAfterReading;
+
+ public StreamCharBufferReader(boolean removeAfterReading)
+ {
+ this.removeAfterReading = removeAfterReading;
+ }
+
+ private int prepareRead(int len)
+ {
+ if (hasReaders && eofReachedCounter != 0)
+ {
+ if (eofReachedCounter != writer.writerUsedCounter)
+ {
+ eofReachedCounter = 0;
+ eofException = false;
+ repositionChunkReader();
+ }
+ }
+ if (chunkReader == null && eofReachedCounter == 0)
+ {
+ if (firstChunk != null)
+ {
+ chunkReader = firstChunk.getChunkReader(removeAfterReading);
+ if (removeAfterReading)
+ {
+ firstChunk.subtractFromTotalCount();
+ }
+ }
+ else
+ {
+ chunkReader = new AllocatedBufferReader(allocBuffer,
+ removeAfterReading);
+ }
+ }
+ int available = 0;
+ if (chunkReader != null)
+ {
+ available = chunkReader.getReadLenLimit(len);
+ while (available == 0 && chunkReader != null)
+ {
+ chunkReader = chunkReader.next();
+ if (chunkReader != null)
+ {
+ available = chunkReader.getReadLenLimit(len);
+ }
+ else
+ {
+ available = 0;
+ }
+ }
+ }
+ if (chunkReader == null)
+ {
+ if (hasReaders)
+ {
+ eofReachedCounter = writer.writerUsedCounter;
+ }
+ else
+ {
+ eofReachedCounter = 1;
+ }
+ }
+ else if (hasReaders)
+ {
+ lastChunkReader = chunkReader;
+ }
+ return available;
+ }
+
+ /* adds support for reading and writing simultaneously in the same thread */
+ private void repositionChunkReader()
+ {
+ if (lastChunkReader instanceof AllocatedBufferReader)
+ {
+ if (lastChunkReader.isValid())
+ {
+ chunkReader = lastChunkReader;
+ }
+ else
+ {
+ AllocatedBufferReader allocBufferReader = (AllocatedBufferReader) lastChunkReader;
+ // find out what is the CharBufferChunk that was read by the AllocatedBufferReader already
+ int currentPosition = allocBufferReader.position;
+ AbstractChunk chunk = lastChunk;
+ while (chunk != null
+ && chunk.writerUsedCounter >= lastChunkReader
+ .getWriterUsedCounter())
+ {
+ if (chunk instanceof CharBufferChunk)
+ {
+ CharBufferChunk charBufChunk = (CharBufferChunk) chunk;
+ if (charBufChunk.allocatedBufferId == allocBufferReader.parent.id)
+ {
+ if (currentPosition >= charBufChunk.offset
+ && currentPosition <= charBufChunk.lastposition)
+ {
+ CharBufferChunkReader charBufChunkReader = (CharBufferChunkReader) charBufChunk
+ .getChunkReader(removeAfterReading);
+ int oldpointer = charBufChunkReader.pointer;
+ // skip the already chars
+ charBufChunkReader.pointer = currentPosition;
+ if (removeAfterReading)
+ {
+ int diff = charBufChunkReader.pointer
+ - oldpointer;
+ totalCharsInList -= diff;
+ charBufChunk.subtractFromTotalCount();
+ }
+ chunkReader = charBufChunkReader;
+ break;
+ }
+ }
+ }
+ chunk = chunk.prev;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean ready() throws IOException
+ {
+ return true;
+ }
+
+ @Override
+ public final int read(final char[] b, final int off, final int len)
+ throws IOException
+ {
+ return readImpl(b, off, len);
+ }
+
+ final int readImpl(final char[] b, final int off, final int len)
+ throws IOException
+ {
+ if (b == null)
+ {
+ throw new NullPointerException();
+ }
+
+ if ((off < 0) || (off > b.length) || (len < 0)
+ || ((off + len) > b.length) || ((off + len) < 0))
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (len == 0)
+ {
+ return 0;
+ }
+
+ int charsLeft = len;
+ int currentOffset = off;
+ int readChars = prepareRead(charsLeft);
+ if (eofException)
+ {
+ throw new EOFException();
+ }
+
+ int totalCharsRead = 0;
+ while (charsLeft > 0 && readChars > 0)
+ {
+ chunkReader.read(b, currentOffset, readChars);
+ charsLeft -= readChars;
+ currentOffset += readChars;
+ totalCharsRead += readChars;
+ if (charsLeft > 0)
+ {
+ readChars = prepareRead(charsLeft);
+ }
+ }
+
+ if (totalCharsRead > 0)
+ {
+ return totalCharsRead;
+ }
+
+ eofException = true;
+ return -1;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // do nothing
+ }
+
+ public final StreamCharBuffer getBuffer()
+ {
+ return StreamCharBuffer.this;
+ }
+
+ public int getReadLenLimit(int askedAmount)
+ {
+ return prepareRead(askedAmount);
+ }
+ }
+
+ abstract class AbstractChunk
+ {
+ AbstractChunk next;
+ AbstractChunk prev;
+ int writerUsedCounter;
+
+ public AbstractChunk()
+ {
+ if (hasReaders)
+ {
+ writerUsedCounter = writer.writerUsedCounter;
+ }
+ else
+ {
+ writerUsedCounter = 1;
+ }
+ }
+
+ public abstract void writeTo(Writer target) throws IOException;
+
+ public abstract ChunkReader getChunkReader(boolean removeAfterReading);
+
+ public abstract int size();
+
+ public int getWriterUsedCounter()
+ {
+ return writerUsedCounter;
+ }
+
+ public void subtractFromTotalCount()
+ {
+ totalCharsInList -= size();
+ }
+ }
+
+ // keep read state in this class
+ static abstract class ChunkReader
+ {
+ public abstract int read(char[] ch, int off, int len)
+ throws IOException;
+
+ public abstract int getReadLenLimit(int askedAmount);
+
+ public abstract ChunkReader next();
+
+ public abstract int getWriterUsedCounter();
+
+ public abstract boolean isValid();
+ }
+
+ final class AllocatedBuffer
+ {
+ private int id = allocatedBufferIdSequence++;
+ private int size;
+ private char[] buffer;
+ private int used = 0;
+ private int chunkStart = 0;
+
+ public AllocatedBuffer(int size)
+ {
+ this.size = size;
+ buffer = new char[size];
+ }
+
+ public int charsUsed()
+ {
+ return used - chunkStart;
+ }
+
+ public void writeTo(Writer target) throws IOException
+ {
+ if (used - chunkStart > 0)
+ {
+ target.write(buffer, chunkStart, used - chunkStart);
+ }
+ }
+
+ public void reuseBuffer()
+ {
+ used = 0;
+ chunkStart = 0;
+ }
+
+ public int chunkSize()
+ {
+ return buffer.length;
+ }
+
+ public int spaceLeft()
+ {
+ return size - used;
+ }
+
+ public boolean write(final char ch)
+ {
+ if (used < size)
+ {
+ buffer[used++] = ch;
+ return true;
+ }
+
+ return false;
+ }
+
+ public final void write(final char[] ch, final int off, final int len)
+ {
+ arrayCopy(ch, off, buffer, used, len);
+ used += len;
+ }
+
+ public final void writeString(final String str, final int off,
+ final int len)
+ {
+ str.getChars(off, off + len, buffer, used);
+ used += len;
+ }
+
+ public final void writeStringBuilder(final StringBuilder stringBuilder,
+ final int off, final int len)
+ {
+ stringBuilder.getChars(off, off + len, buffer, used);
+ used += len;
+ }
+
+ public final void writeStringBuffer(final StringBuffer stringBuffer,
+ final int off, final int len)
+ {
+ stringBuffer.getChars(off, off + len, buffer, used);
+ used += len;
+ }
+
+ /**
+ * Creates a new chunk from the content written to the buffer
+ * (used before adding StringChunk or StreamCharBufferChunk).
+ *
+ * @return the chunk
+ */
+ public CharBufferChunk createChunk()
+ {
+ CharBufferChunk chunk = new CharBufferChunk(id, buffer, chunkStart,
+ used - chunkStart);
+ chunkStart = used;
+ return chunk;
+ }
+
+ public boolean hasChunk()
+ {
+ return (used > chunkStart);
+ }
+
+ }
+
+ /**
+ * The data in the buffer is stored in a linked list of StreamCharBufferChunks.
+ *
+ * This class contains data & read/write state for the "chunk level".
+ * It contains methods for reading & writing to the chunk level.
+ *
+ * Underneath the chunk is one more level, the StringChunkGroup + StringChunk.
+ * StringChunk makes it possible to directly store the java.lang.String objects.
+ *
+ * @author Lari Hotari
+ *
+ */
+ final class CharBufferChunk extends AbstractChunk
+ {
+ int allocatedBufferId;
+ char[] buffer;
+ int offset;
+ int lastposition;
+ int length;
+
+ public CharBufferChunk(int allocatedBufferId, char[] buffer,
+ int offset, int len)
+ {
+ super();
+ this.allocatedBufferId = allocatedBufferId;
+ this.buffer = buffer;
+ this.offset = offset;
+ this.lastposition = offset + len;
+ this.length = len;
+ }
+
+ @Override
+ public void writeTo(final Writer target) throws IOException
+ {
+ target.write(buffer, offset, length);
+ }
+
+ @Override
+ public ChunkReader getChunkReader(boolean removeAfterReading)
+ {
+ return new CharBufferChunkReader(this, removeAfterReading);
+ }
+
+ @Override
+ public int size()
+ {
+ return length;
+ }
+
+ public boolean isSingleBuffer()
+ {
+ return offset == 0 && length == buffer.length;
+ }
+ }
+
+ abstract class AbstractChunkReader extends ChunkReader
+ {
+ private AbstractChunk parentChunk;
+ private boolean removeAfterReading;
+
+ public AbstractChunkReader(AbstractChunk parentChunk,
+ boolean removeAfterReading)
+ {
+ this.parentChunk = parentChunk;
+ this.removeAfterReading = removeAfterReading;
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ return true;
+ }
+
+ @Override
+ public ChunkReader next()
+ {
+ if (removeAfterReading)
+ {
+ if (firstChunk == parentChunk)
+ {
+ firstChunk = null;
+ }
+ if (lastChunk == parentChunk)
+ {
+ lastChunk = null;
+ }
+ }
+ AbstractChunk nextChunk = parentChunk.next;
+ if (nextChunk != null)
+ {
+ if (removeAfterReading)
+ {
+ if (firstChunk == null)
+ {
+ firstChunk = nextChunk;
+ }
+ if (lastChunk == null)
+ {
+ lastChunk = nextChunk;
+ }
+ nextChunk.prev = null;
+ nextChunk.subtractFromTotalCount();
+ }
+ return nextChunk.getChunkReader(removeAfterReading);
+ }
+
+ return new AllocatedBufferReader(allocBuffer, removeAfterReading);
+ }
+
+ @Override
+ public int getWriterUsedCounter()
+ {
+ return parentChunk.getWriterUsedCounter();
+ }
+ }
+
+ final class CharBufferChunkReader extends AbstractChunkReader
+ {
+ CharBufferChunk parent;
+ int pointer;
+
+ public CharBufferChunkReader(CharBufferChunk parent,
+ boolean removeAfterReading)
+ {
+ super(parent, removeAfterReading);
+ this.parent = parent;
+ this.pointer = parent.offset;
+ }
+
+ @Override
+ public int read(final char[] ch, final int off, final int len)
+ throws IOException
+ {
+ arrayCopy(parent.buffer, pointer, ch, off, len);
+ pointer += len;
+ return len;
+ }
+
+ @Override
+ public int getReadLenLimit(int askedAmount)
+ {
+ return Math.min(parent.lastposition - pointer, askedAmount);
+ }
+ }
+
+ /**
+ * StringChunk is a wrapper for java.lang.String.
+ *
+ * It also keeps state of the read offset and the number of unread characters.
+ *
+ * There's methods that StringChunkGroup uses for reading data.
+ *
+ * @author Lari Hotari
+ *
+ */
+ final class StringChunk extends AbstractChunk
+ {
+ String str;
+ int offset;
+ int lastposition;
+ int length;
+
+ public StringChunk(String str, int offset, int length)
+ {
+ this.str = str;
+ this.offset = offset;
+ this.length = length;
+ this.lastposition = offset + length;
+ }
+
+ @Override
+ public ChunkReader getChunkReader(boolean removeAfterReading)
+ {
+ return new StringChunkReader(this, removeAfterReading);
+ }
+
+ @Override
+ public void writeTo(Writer target) throws IOException
+ {
+ target.write(str, offset, length);
+ }
+
+ @Override
+ public int size()
+ {
+ return length;
+ }
+
+ public boolean isSingleBuffer()
+ {
+ return offset == 0 && length == str.length();
+ }
+ }
+
+ final class StringChunkReader extends AbstractChunkReader
+ {
+ StringChunk parent;
+ int position;
+
+ public StringChunkReader(StringChunk parent, boolean removeAfterReading)
+ {
+ super(parent, removeAfterReading);
+ this.parent = parent;
+ this.position = parent.offset;
+ }
+
+ @Override
+ public int read(final char[] ch, final int off, final int len)
+ {
+ parent.str.getChars(position, (position + len), ch, off);
+ position += len;
+ return len;
+ }
+
+ @Override
+ public int getReadLenLimit(int askedAmount)
+ {
+ return Math.min(parent.lastposition - position, askedAmount);
+ }
+ }
+
+ final class StreamCharBufferSubChunk extends AbstractChunk
+ {
+ StreamCharBuffer streamCharBuffer;
+ int cachedSize;
+
+ public StreamCharBufferSubChunk(StreamCharBuffer streamCharBuffer)
+ {
+ this.streamCharBuffer = streamCharBuffer;
+ if (totalCharsInDynamicChunks != -1)
+ {
+ cachedSize = streamCharBuffer.size();
+ totalCharsInDynamicChunks += cachedSize;
+ }
+ else
+ {
+ cachedSize = -1;
+ }
+ }
+
+ @Override
+ public void writeTo(Writer target) throws IOException
+ {
+ streamCharBuffer.writeTo(target);
+ }
+
+ @Override
+ public ChunkReader getChunkReader(boolean removeAfterReading)
+ {
+ return new StreamCharBufferSubChunkReader(this, removeAfterReading);
+ }
+
+ @Override
+ public int size()
+ {
+ if (cachedSize == -1)
+ {
+ cachedSize = streamCharBuffer.size();
+ }
+ return cachedSize;
+ }
+
+ public boolean hasCachedSize()
+ {
+ return (cachedSize != -1);
+ }
+
+ public StreamCharBuffer getSubBuffer()
+ {
+ return this.streamCharBuffer;
+ }
+
+ public boolean resetSize()
+ {
+ if (cachedSize != -1)
+ {
+ cachedSize = -1;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void subtractFromTotalCount()
+ {
+ if (totalCharsInDynamicChunks != -1)
+ {
+ totalCharsInDynamicChunks -= size();
+ }
+ dynamicChunkMap.remove(streamCharBuffer.bufferKey);
+ }
+ }
+
+ final class StreamCharBufferSubChunkReader extends AbstractChunkReader
+ {
+ StreamCharBufferSubChunk parent;
+ private StreamCharBufferReader reader;
+
+ public StreamCharBufferSubChunkReader(StreamCharBufferSubChunk parent,
+ boolean removeAfterReading)
+ {
+ super(parent, removeAfterReading);
+ this.parent = parent;
+ reader = (StreamCharBufferReader) parent.streamCharBuffer
+ .getReader();
+ }
+
+ @Override
+ public int getReadLenLimit(int askedAmount)
+ {
+ return reader.getReadLenLimit(askedAmount);
+ }
+
+ @Override
+ public int read(char[] ch, int off, int len) throws IOException
+ {
+ return reader.read(ch, off, len);
+ }
+ }
+
+ final class AllocatedBufferReader extends ChunkReader
+ {
+ AllocatedBuffer parent;
+ int position;
+ int writerUsedCounter;
+ boolean removeAfterReading;
+
+ public AllocatedBufferReader(AllocatedBuffer parent,
+ boolean removeAfterReading)
+ {
+ this.parent = parent;
+ this.position = parent.chunkStart;
+ if (hasReaders)
+ {
+ writerUsedCounter = writer.writerUsedCounter;
+ }
+ else
+ {
+ writerUsedCounter = 1;
+ }
+ this.removeAfterReading = removeAfterReading;
+ }
+
+ @Override
+ public int getReadLenLimit(int askedAmount)
+ {
+ return Math.min(parent.used - position, askedAmount);
+ }
+
+ @Override
+ public int read(char[] ch, int off, int len) throws IOException
+ {
+ arrayCopy(parent.buffer, position, ch, off, len);
+ position += len;
+ if (removeAfterReading)
+ {
+ parent.chunkStart = position;
+ }
+ return len;
+ }
+
+ @Override
+ public ChunkReader next()
+ {
+ return null;
+ }
+
+ @Override
+ public int getWriterUsedCounter()
+ {
+ return writerUsedCounter;
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ return (allocBuffer == parent && (lastChunk == null || lastChunk.writerUsedCounter < writerUsedCounter));
+ }
+ }
+
+ /**
+ * Simplified version of a CharArrayWriter used internally in readAsCharArray method.
+ *
+ * Doesn't do any bound checks since size shouldn't change during writing in readAsCharArray.
+ */
+ private static final class FixedCharArrayWriter extends Writer
+ {
+ char buf[];
+ int count = 0;
+
+ public FixedCharArrayWriter(int fixedSize)
+ {
+ buf = new char[fixedSize];
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException
+ {
+ arrayCopy(cbuf, off, buf, count, len);
+ count += len;
+ }
+
+ @Override
+ public void write(char[] cbuf) throws IOException
+ {
+ write(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException
+ {
+ str.getChars(off, off + len, buf, count);
+ count += len;
+ }
+
+ @Override
+ public void write(String str) throws IOException
+ {
+ write(str, 0, str.length());
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // do nothing
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ // do nothing
+ }
+
+ public char[] getCharArray()
+ {
+ return buf;
+ }
+ }
+
+ /**
+ * Interface for a Writer that gets initialized if it is used
+ * Can be used for passing in to "connectTo" method of StreamCharBuffer
+ *
+ * @author Lari Hotari
+ *
+ */
+ public static interface LazyInitializingWriter
+ {
+ public Writer getWriter() throws IOException;
+ }
+
+ /**
+ * Simple holder class for the connected writer
+ *
+ * @author Lari Hotari
+ *
+ */
+ static final class ConnectedWriter
+ {
+ Writer writer;
+ LazyInitializingWriter lazyInitializingWriter;
+ final boolean autoFlush;
+
+ ConnectedWriter(final Writer writer, final boolean autoFlush)
+ {
+ this.writer = writer;
+ this.autoFlush = autoFlush;
+ }
+
+ ConnectedWriter(final LazyInitializingWriter lazyInitializingWriter,
+ final boolean autoFlush)
+ {
+ this.lazyInitializingWriter = lazyInitializingWriter;
+ this.autoFlush = autoFlush;
+ }
+
+ Writer getWriter() throws IOException
+ {
+ if (writer == null && lazyInitializingWriter != null)
+ {
+ writer = lazyInitializingWriter.getWriter();
+ }
+ return writer;
+ }
+
+ public void flush() throws IOException
+ {
+ if (writer != null && isAutoFlush())
+ {
+ writer.flush();
+ }
+ }
+
+ public boolean isAutoFlush()
+ {
+ return autoFlush;
+ }
+ }
+
+ static final class SingleOutputWriter extends Writer
+ {
+ private ConnectedWriter writer;
+
+ public SingleOutputWriter(ConnectedWriter writer)
+ {
+ this.writer = writer;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // do nothing
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ writer.flush();
+ }
+
+ @Override
+ public void write(final char[] cbuf, final int off, final int len)
+ throws IOException
+ {
+ writer.getWriter().write(cbuf, off, len);
+ }
+
+ @Override
+ public Writer append(final CharSequence csq, final int start,
+ final int end) throws IOException
+ {
+ writer.getWriter().append(csq, start, end);
+ return this;
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException
+ {
+ StringCharArrayAccessor.writeStringAsCharArray(writer.getWriter(),
+ str, off, len);
+ }
+ }
+
+ /**
+ * delegates to several writers, used in "connectTo" mode.
+ *
+ */
+ static final class MultiOutputWriter extends Writer
+ {
+ final List<ConnectedWriter> writers;
+
+ public MultiOutputWriter(final List<ConnectedWriter> writers)
+ {
+ this.writers = writers;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // do nothing
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ for (ConnectedWriter writer : writers)
+ {
+ writer.flush();
+ }
+ }
+
+ @Override
+ public void write(final char[] cbuf, final int off, final int len)
+ throws IOException
+ {
+ for (ConnectedWriter writer : writers)
+ {
+ writer.getWriter().write(cbuf, off, len);
+ }
+ }
+
+ @Override
+ public Writer append(final CharSequence csq, final int start,
+ final int end) throws IOException
+ {
+ for (ConnectedWriter writer : writers)
+ {
+ writer.getWriter().append(csq, start, end);
+ }
+ return this;
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException
+ {
+ for (ConnectedWriter writer : writers)
+ {
+ StringCharArrayAccessor.writeStringAsCharArray(
+ writer.getWriter(), str, off, len);
+ }
+ }
+ }
+
+ /* Compatibility methods so that StreamCharBuffer will behave more like java.lang.String in groovy code */
+
+ public char charAt(int index)
+ {
+ return toString().charAt(index);
+ }
+
+ public int length()
+ {
+ return size();
+ }
+
+ public CharSequence subSequence(int start, int end)
+ {
+ return toString().subSequence(start, end);
+ }
+
+ public boolean asBoolean()
+ {
+ return isNotEmpty();
+ }
+
+ /* methods for notifying child (sub) StreamCharBuffer changes to the parent StreamCharBuffer */
+
+ void addParentBuffer(StreamCharBuffer parent)
+ {
+ if (parentBuffers == null)
+ {
+ parentBuffers = new HashSet<SoftReference<StreamCharBufferKey>>();
+ }
+ parentBuffers.add(new SoftReference<StreamCharBufferKey>(
+ parent.bufferKey));
+ }
+
+ boolean bufferChanged(StreamCharBuffer buffer)
+ {
+ StreamCharBufferSubChunk subChunk = dynamicChunkMap
+ .get(buffer.bufferKey);
+ if (subChunk == null)
+ {
+ // buffer isn't a subchunk in this buffer any more
+ return false;
+ }
+ // reset cached size;
+ if (subChunk.resetSize())
+ {
+ totalCharsInDynamicChunks = -1;
+ sizeAtLeast = -1;
+ // notify parents too
+ notifyBufferChange();
+ }
+ return true;
+ }
+
+ void notifyBufferChange()
+ {
+ if (parentBuffers == null)
+ {
+ return;
+ }
+
+ for (Iterator<SoftReference<StreamCharBufferKey>> i = parentBuffers
+ .iterator(); i.hasNext();)
+ {
+ SoftReference<StreamCharBufferKey> ref = i.next();
+ final StreamCharBuffer.StreamCharBufferKey parentKey = ref.get();
+ boolean removeIt = true;
+ if (parentKey != null)
+ {
+ StreamCharBuffer parent = parentKey.getBuffer();
+ removeIt = !parent.bufferChanged(this);
+ }
+ if (removeIt)
+ {
+ i.remove();
+ }
+ }
+ }
+
+ public void readExternal(ObjectInput in) throws IOException,
+ ClassNotFoundException
+ {
+ String str = in.readUTF();
+ reset();
+ if (str.length() > 0)
+ {
+ addChunk(new StringChunk(str, 0, str.length()));
+ }
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ String str = toString();
+ out.writeUTF(str);
+ }
+
+}
Propchange: myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StreamCharBuffer.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringCharArrayAccessor.java
URL: http://svn.apache.org/viewvc/myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringCharArrayAccessor.java?rev=1239799&view=auto
==============================================================================
--- myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringCharArrayAccessor.java (added)
+++ myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringCharArrayAccessor.java Thu Feb 2 20:00:42 2012
@@ -0,0 +1,240 @@
+/*
+ * 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.myfaces.shared.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.Field;
+
+/**
+ * Provides optimized access to java.lang.String internals
+ *
+ * - Optimized way of creating java.lang.String by reusing a char[] buffer
+ * - Optimized way of writing String to java.io.Writer
+ *
+ * java.lang.String creation reusing a char[] buffer requires Java 1.5+
+ *
+ * System property "stringchararrayaccessor.disabled" disables this hack.
+ * -Dstringchararrayaccessor.disabled=true
+ *
+ * Read JSR-133, "9.1.1 Post-Construction Modification of Final Fields"
+ * http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ * @see org.codehaus.groovy.grails.web.util.StreamCharBuffer
+ * file licensed under ASL v2.0
+ * Copyright 2009 the original author or authors.
+ */
+public class StringCharArrayAccessor
+{
+
+ static volatile boolean enabled = !Boolean
+ .getBoolean("oam.stringchararrayaccessor.disabled");
+
+ static Field valueField;
+ static Field countField;
+ static Field offsetField;
+
+ static
+ {
+ if (enabled)
+ {
+ try
+ {
+ valueField = String.class.getDeclaredField("value");
+ valueField.setAccessible(true);
+
+ countField = String.class.getDeclaredField("count");
+ countField.setAccessible(true);
+
+ offsetField = String.class.getDeclaredField("offset");
+ offsetField.setAccessible(true);
+ }
+ catch (Exception e)
+ {
+ enabled = false;
+ System.err
+ .println("Unable to use direct char[] access of java.lang.String");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Writes a portion of a string to a target java.io.Writer with direct access to the char[] of the java.lang.String
+ *
+ * @param writer
+ * target java.io.Writer for output
+ *
+ * @param str
+ * A String
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ */
+ static public void writeStringAsCharArray(Writer writer, String str)
+ throws IOException
+ {
+ writeStringAsCharArray(writer, str, 0, str.length());
+ }
+
+ /**
+ * Writes a portion of a string to a target java.io.Writer with direct access to the char[] of the java.lang.String
+ *
+ * @param writer
+ * target java.io.Writer for output
+ *
+ * @param str
+ * A String
+ *
+ * @param off
+ * Offset from which to start writing characters
+ *
+ * @param len
+ * Number of characters to write
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ */
+ static public void writeStringAsCharArray(Writer writer, String str,
+ int off, int len) throws IOException
+ {
+ if (!enabled)
+ {
+ writeStringFallback(writer, str, off, len);
+ return;
+ }
+
+ char[] value;
+ int internalOffset;
+ try
+ {
+ value = (char[]) valueField.get(str);
+ internalOffset = offsetField.getInt(str);
+ }
+ catch (Exception e)
+ {
+ handleError(e);
+ writeStringFallback(writer, str, off, len);
+ return;
+ }
+ writer.write(value, internalOffset + off, len);
+ }
+
+ private static void writeStringFallback(Writer writer, String str, int off,
+ int len) throws IOException
+ {
+ writer.write(str, off, len);
+ }
+
+ static char[] getValue(String str)
+ {
+ if (!enabled)
+ {
+ return getValueFallback(str);
+ }
+
+ char[] value = null;
+ int internalOffset = 0;
+ try
+ {
+ value = (char[]) valueField.get(str);
+ internalOffset = offsetField.getInt(str);
+ }
+ catch (Exception e)
+ {
+ handleError(e);
+ }
+ if (value != null && internalOffset == 0)
+ {
+ return value;
+ }
+
+ return getValueFallback(str);
+ }
+
+ static char[] getValueFallback(String str)
+ {
+ return str.toCharArray();
+ }
+
+ /**
+ * creates a new java.lang.String by setting the char array directly to the String instance with reflection.
+ *
+ * @param charBuf
+ * char array to be used as java.lang.String content, don't modify it after passing it.
+ * @return new java.lang.String
+ */
+ public static String createString(char[] charBuf)
+ {
+ if (!enabled)
+ {
+ return createStringFallback(charBuf);
+ }
+
+ String str = new String();
+ try
+ {
+ // try to prevent possible final field setting execution reordering in JIT
+ // (JSR-133/JMM, "9.1.1 Post-Construction Modification of Final Fields")
+ // it was a bit unclear for me if this could ever happen in a single thread
+ synchronized (str)
+ {
+ valueField.set(str, charBuf);
+ countField.set(str, charBuf.length);
+ }
+ synchronized (str)
+ {
+ // safety check, just to be sure that setting the final fields went ok
+ if (str.length() != charBuf.length)
+ {
+ throw new IllegalStateException(
+ "Fast java.lang.String construction failed.");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ handleError(e);
+ str = createStringFallback(charBuf);
+ }
+ return str;
+ }
+
+ private static String createStringFallback(char[] charBuf)
+ {
+ return new String(charBuf);
+ }
+
+ private static synchronized void handleError(Exception e)
+ {
+ enabled = false;
+ System.err
+ .println("Unable to use direct char[] access of java.lang.String. Disabling this method.");
+ valueField = null;
+ countField = null;
+ offsetField = null;
+ e.printStackTrace();
+ }
+
+ static public boolean isEnabled()
+ {
+ return enabled;
+ }
+}
Propchange: myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringCharArrayAccessor.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringUtils.java
URL: http://svn.apache.org/viewvc/myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringUtils.java?rev=1239799&r1=1239798&r2=1239799&view=diff
==============================================================================
--- myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringUtils.java (original)
+++ myfaces/shared/trunk_4.0.x/core/src/main/java/org/apache/myfaces/shared/util/StringUtils.java Thu Feb 2 20:00:42 2012
@@ -268,13 +268,13 @@ public final class StringUtils
int endInt = str.indexOf(quote, begin);
// If no quotes, return the original string
- // and save StringBuffer allocation/char copying
+ // and save StringBuilder allocation/char copying
if (endInt < 0)
{
return str.substring(begin, end);
}
- StringBuffer sb = new StringBuffer(end - begin);
+ StringBuilder sb = new StringBuilder(end - begin);
int beginInt = begin; // need begin later
do
{
@@ -347,7 +347,7 @@ public final class StringUtils
int pos = str.indexOf(repl);
// If no replacement needed, return the original string
- // and save StringBuffer allocation/char copying
+ // and save StringBuilder allocation/char copying
if (pos < 0)
{
return str;
@@ -355,8 +355,8 @@ public final class StringUtils
int len = repl.length();
int lendiff = with.length() - repl.length();
- StringBuffer out =
- new StringBuffer((lendiff <= 0) ? str.length()
+ StringBuilder out =
+ new StringBuilder((lendiff <= 0) ? str.length()
: (str.length() + (10 * lendiff)));
while(pos >= 0)
{
@@ -373,7 +373,7 @@ public final class StringUtils
int pos = str.indexOf(repl);
// If no replacement needed, return the original string
- // and save StringBuffer allocation/char copying
+ // and save StringBuilder allocation/char copying
if (pos < 0)
{
return str;
@@ -381,8 +381,8 @@ public final class StringUtils
int len = str.length();
int lendiff = with.length() - 1;
- StringBuffer out =
- new StringBuffer((lendiff <= 0) ? str.length()
+ StringBuilder out =
+ new StringBuilder((lendiff <= 0) ? str.length()
: (str.length() + (10 * lendiff)));
int lastindex = 0;
while( pos >= 0)
@@ -394,7 +394,7 @@ public final class StringUtils
return out.append(substring(str, lastindex, len)).toString();
}
-
+
public static StringBuffer replace(
StringBuffer out, String s, String repl, String with)
{
@@ -412,6 +412,23 @@ public final class StringUtils
return out.append(substring(s, lastindex, len));
}
+ public static StringBuilder replace(
+ StringBuilder out, String s, String repl, String with)
+ {
+ int lastindex = 0;
+ int len = repl.length();
+ int index = s.indexOf(repl);
+ while (index >= 0)
+ {
+ // we have search string at position index
+ out.append(substring(s, lastindex, index)).append(with);
+ lastindex = index + len;
+ index = s.indexOf(repl, lastindex);
+ }
+
+ return out.append(substring(s, lastindex, len));
+ }
+
/**
* Split a string into an array of strings arround a character separator.
* This function will be efficient for longer strings