Skip to content
This repository
Browse code

Added chained buffers.

  • Loading branch information...
commit e50fec8396f093203e3de0372a064139e30e93cc 1 parent 292cc28
James Roper authored July 02, 2011

Showing 26 changed files with 484 additions and 95 deletions. Show diff stats Hide diff stats

  1. 4  .gitignore
  2. 85  src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java
  3. 16  src/java/com/opensymphony/module/sitemesh/PageParser.java
  4. 58  src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java
  5. 38  src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java
  6. 32  src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java
  7. 28  src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java
  8. 33  src/java/com/opensymphony/module/sitemesh/filter/Buffer.java
  9. 2  src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java
  10. 3  src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java
  11. 26  src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java
  12. 29  src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java
  13. 21  src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java
  14. 20  src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java
  15. 23  src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java
  16. 12  src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java
  17. 19  src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java
  18. 24  src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java
  19. 13  src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java
  20. 3  src/java/com/opensymphony/sitemesh/ContentProcessor.java
  21. 9  src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java
  22. 3  src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java
  23. 61  src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java
  24. 3  src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java
  25. 5  src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java
  26. 9  src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java
4  .gitignore
... ...
@@ -1,2 +1,6 @@
1 1
 build
  2
+out
  3
+*.iml
  4
+*.ipr
  5
+*.iws
2 6
 dist
85  src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java
... ...
@@ -0,0 +1,85 @@
  1
+package com.opensymphony.module.sitemesh;
  2
+
  3
+import java.io.IOException;
  4
+import java.io.Writer;
  5
+import java.util.ArrayList;
  6
+import java.util.Collections;
  7
+import java.util.List;
  8
+
  9
+/**
  10
+ * The default implementation of sitemesh buffer
  11
+ */
  12
+public class DefaultSitemeshBuffer implements SitemeshBuffer {
  13
+
  14
+    private final char[] buffer;
  15
+    private final int length;
  16
+    private final List<SitemeshBufferFragment> chainedBuffers;
  17
+
  18
+    public DefaultSitemeshBuffer(char[] buffer) {
  19
+        this(buffer, buffer.length);
  20
+    }
  21
+
  22
+    public DefaultSitemeshBuffer(char[] buffer, int length) {
  23
+        this(buffer, length, Collections.<SitemeshBufferFragment>emptyList());
  24
+    }
  25
+
  26
+    public DefaultSitemeshBuffer(char[] buffer, int length, List<SitemeshBufferFragment> chainedBuffers) {
  27
+        this.buffer = buffer;
  28
+        this.length = length;
  29
+        this.chainedBuffers = new ArrayList<SitemeshBufferFragment>(chainedBuffers);
  30
+        Collections.sort(chainedBuffers);
  31
+    }
  32
+
  33
+    public void writeTo(Writer writer, int start, int length) throws IOException {
  34
+        int pos = start;
  35
+        for (SitemeshBufferFragment fragment : chainedBuffers) {
  36
+            if (fragment.getPosition() < pos) {
  37
+                continue;
  38
+            }
  39
+            if (fragment.getPosition() >= start + length) {
  40
+                break;
  41
+            }
  42
+            // Write the buffer up to the fragment
  43
+            writer.write(buffer, pos, fragment.getPosition() - pos);
  44
+            // Write the fragment
  45
+            fragment.getBuffer().writeTo(writer, fragment.getStart(), fragment.getLength());
  46
+            // increment pos
  47
+            pos = fragment.getPosition();
  48
+        }
  49
+        // Write out the remaining buffer
  50
+        if (pos < start + length) {
  51
+            writer.write(buffer, pos, (start + length) - pos);
  52
+        }
  53
+    }
  54
+
  55
+    public int getTotalLength() {
  56
+        return getTotalLength(0, length);
  57
+    }
  58
+
  59
+    public int getTotalLength(int start, int length) {
  60
+        int total = length;
  61
+
  62
+        for (SitemeshBufferFragment fragment : chainedBuffers) {
  63
+            if (fragment.getPosition() < start) {
  64
+                continue;
  65
+            }
  66
+            if (fragment.getPosition() > start + length) {
  67
+                break;
  68
+            }
  69
+            total += fragment.getBuffer().getTotalLength(fragment.getStart(), fragment.getLength());
  70
+        }
  71
+        return total;
  72
+    }
  73
+
  74
+    public int getBufferLength() {
  75
+        return length;
  76
+    }
  77
+
  78
+    public char[] getCharArray() {
  79
+        return buffer;
  80
+    }
  81
+
  82
+    public boolean hasFragments() {
  83
+        return !chainedBuffers.isEmpty();
  84
+    }
  85
+}
16  src/java/com/opensymphony/module/sitemesh/PageParser.java
@@ -25,24 +25,12 @@
25 25
  * @version $Revision: 1.2 $
26 26
  */
27 27
 public interface PageParser {
28  
-
29  
-    /**
30  
-     * This builds a Page.
31  
-     *
32  
-     * @param data The data for the page. Note, this array may be larger than the length of the content.
33  
-     * @param length The length of the page.
34  
-     * @return The parsed page
35  
-     * @throws IOException if an error occurs
36  
-     * @since 2.5
37  
-     */
38  
-    Page parse(char[] data, int length) throws IOException;
39  
-
40 28
     /**
41 29
      * This builds a Page.
42 30
      *
43  
-     * @param data The data for the page.
  31
+     * @param buffer The buffer for the page.
44 32
      * @return The parsed page
45 33
      * @throws IOException if an error occurs
46 34
      */
47  
-    Page parse(char[] data) throws IOException;    
  35
+    Page parse(SitemeshBuffer buffer) throws IOException;
48 36
 }
58  src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java
... ...
@@ -0,0 +1,58 @@
  1
+package com.opensymphony.module.sitemesh;
  2
+
  3
+import java.io.IOException;
  4
+import java.io.Writer;
  5
+
  6
+/**
  7
+ * A potentially chained sitemesh buffer
  8
+ */
  9
+public interface SitemeshBuffer {
  10
+
  11
+    /**
  12
+     * Get the char array for this buffer.  This array may be longer than the length of the content, you must use
  13
+     * getBufferLength() in combination with this method.
  14
+     *
  15
+     * @return The char array for this buffer
  16
+     */
  17
+    char[] getCharArray();
  18
+
  19
+    /**
  20
+     * Get the length of the buffered content.
  21
+     *
  22
+     * @return The length of the buffered content.
  23
+     */
  24
+    int getBufferLength();
  25
+
  26
+    /**
  27
+     * Get the total length of the buffered content, including the length of any chained buffers.
  28
+     *
  29
+     * @return The total length.
  30
+     */
  31
+    int getTotalLength();
  32
+
  33
+    /**
  34
+     * Get the total length of the buffered content, including chained buffers from start to length
  35
+     *
  36
+     * @param start Where to start counting the length from
  37
+     * @param length Where to finish
  38
+     * @return THe total length in the given range
  39
+     */
  40
+    int getTotalLength(int start, int length);
  41
+
  42
+    /**
  43
+     * Write this buffer, and any chained sub buffers in the given range, out to the given writer
  44
+     *
  45
+     * @param start The position to start writing from
  46
+     * @param length The length to write
  47
+     * @param writer The writer to write to
  48
+     * @throws IOException If an error occurred
  49
+     */
  50
+    void writeTo(Writer writer, int start, int length) throws IOException;
  51
+
  52
+    /**
  53
+     * Whether the buffer has fragments or not
  54
+     *
  55
+     * @return True if it has fragments
  56
+     */
  57
+    boolean hasFragments();
  58
+}
38  src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java
... ...
@@ -0,0 +1,38 @@
  1
+package com.opensymphony.module.sitemesh;
  2
+
  3
+/**
  4
+ * A fragment of a sitemesh buffer
  5
+ */
  6
+public class SitemeshBufferFragment implements Comparable<SitemeshBufferFragment> {
  7
+    private final SitemeshBuffer buffer;
  8
+    private final int position;
  9
+    private final int start;
  10
+    private final int length;
  11
+
  12
+    public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position) {
  13
+        this.buffer = buffer;
  14
+        this.start = start;
  15
+        this.length = length;
  16
+        this.position = position;
  17
+    }
  18
+
  19
+    public SitemeshBuffer getBuffer() {
  20
+        return buffer;
  21
+    }
  22
+
  23
+    public int getStart() {
  24
+        return start;
  25
+    }
  26
+
  27
+    public int getLength() {
  28
+        return length;
  29
+    }
  30
+
  31
+    public int getPosition() {
  32
+        return position;
  33
+    }
  34
+
  35
+    public int compareTo(SitemeshBufferFragment o) {
  36
+        return o.position - position;
  37
+    }
  38
+}
32  src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java
... ...
@@ -0,0 +1,32 @@
  1
+package com.opensymphony.module.sitemesh;
  2
+
  3
+import com.opensymphony.module.sitemesh.util.CharArrayWriter;
  4
+
  5
+import java.io.IOException;
  6
+import java.util.ArrayList;
  7
+import java.util.List;
  8
+
  9
+/**
  10
+ * A char array writer that caches other sitemesh buffers written to it, so that it doesn't have to continually copy
  11
+ * them from buffer to buffer.
  12
+ */
  13
+public class SitemeshBufferWriter extends CharArrayWriter implements SitemeshWriter {
  14
+
  15
+    private final List<SitemeshBufferFragment> fragments = new ArrayList<SitemeshBufferFragment>();
  16
+
  17
+    public SitemeshBufferWriter() {
  18
+    }
  19
+
  20
+    public SitemeshBufferWriter(int initialSize) {
  21
+        super(initialSize);
  22
+    }
  23
+
  24
+    public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException {
  25
+        fragments.add(new SitemeshBufferFragment(sitemeshBuffer, start, length, count));
  26
+        return false;
  27
+    }
  28
+
  29
+    public SitemeshBuffer getSitemeshBuffer() {
  30
+        return new DefaultSitemeshBuffer(buf, count, fragments);
  31
+    }
  32
+}
28  src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java
... ...
@@ -0,0 +1,28 @@
  1
+package com.opensymphony.module.sitemesh;
  2
+
  3
+import java.io.IOException;
  4
+
  5
+/**
  6
+ * A sitemesh buffer writer.  Provides the ability to defer the writing of a page body until it is finally written to the
  7
+ * stream, so it isn't copied from buffer to buffer
  8
+ */
  9
+public interface SitemeshWriter {
  10
+    /**
  11
+     * Write a sitemesh buffer to the writer.  This may not be written immediately, it may be stored and written later,
  12
+     * when this buffer is written out to a writer.
  13
+     *
  14
+     * @param sitemeshBuffer The buffer to write
  15
+     * @param start The place in the buffer to write from
  16
+     * @param length The length of the buffer to write
  17
+     * @return True if the buffer was written immediately, or false if it will be written later
  18
+     * @throws IOException If an IOException occurred
  19
+     */
  20
+    boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException;
  21
+
  22
+    /**
  23
+     * Get the underlying buffer for the writer
  24
+     *
  25
+     * @return The underlying buffer
  26
+     */
  27
+    public SitemeshBuffer getSitemeshBuffer();
  28
+}
33  src/java/com/opensymphony/module/sitemesh/filter/Buffer.java
@@ -3,12 +3,10 @@
3 3
  * distribution in the LICENSE.txt file. */
4 4
 package com.opensymphony.module.sitemesh.filter;
5 5
 
6  
-import com.opensymphony.module.sitemesh.Page;
7  
-import com.opensymphony.module.sitemesh.PageParser;
  6
+import com.opensymphony.module.sitemesh.*;
8 7
 import com.opensymphony.module.sitemesh.util.FastByteArrayOutputStream;
9 8
 
10 9
 import javax.servlet.ServletOutputStream;
11  
-import java.io.CharArrayWriter;
12 10
 import java.io.IOException;
13 11
 import java.io.PrintWriter;
14 12
 
@@ -25,7 +23,7 @@
25 23
     private final String encoding;
26 24
     private final static TextEncoder TEXT_ENCODER = new TextEncoder();
27 25
 
28  
-    private AccessibleCharArrayWriter bufferedWriter;
  26
+    private SitemeshBufferWriter bufferedWriter;
29 27
     private FastByteArrayOutputStream bufferedStream;
30 28
     private PrintWriter exposedWriter;
31 29
     private ServletOutputStream exposedStream;
@@ -35,19 +33,18 @@ public Buffer(PageParser pageParser, String encoding) {
35 33
         this.encoding = encoding;
36 34
     }
37 35
 
38  
-    public BufferedContent getContents() throws IOException {
  36
+    public SitemeshBuffer getContents() throws IOException {
39 37
         if (bufferedWriter != null) {
40  
-            return new BufferedContent(bufferedWriter.getCharArray(), bufferedWriter.size());
  38
+            return bufferedWriter.getSitemeshBuffer();
41 39
         } else if (bufferedStream != null) {
42  
-            return new BufferedContent(TEXT_ENCODER.encode(bufferedStream.toByteArray(), encoding));
  40
+            return new DefaultSitemeshBuffer(TEXT_ENCODER.encode(bufferedStream.toByteArray(), encoding));
43 41
         } else {
44  
-            return new BufferedContent(new char[0]);
  42
+            return new DefaultSitemeshBuffer(new char[0]);
45 43
         }
46 44
     }
47 45
 
48 46
     public Page parse() throws IOException {
49  
-        BufferedContent content = getContents();
50  
-        return pageParser.parse(content.getBuffer(), content.getLength());
  47
+        return pageParser.parse(getContents());
51 48
     }
52 49
 
53 50
     public PrintWriter getWriter() {
@@ -55,8 +52,8 @@ public PrintWriter getWriter() {
55 52
             if (bufferedStream != null) {
56 53
                 throw new IllegalStateException("response.getWriter() called after response.getOutputStream()");
57 54
             }
58  
-            bufferedWriter = new AccessibleCharArrayWriter(128);
59  
-            exposedWriter = new PrintWriter(bufferedWriter);
  55
+            bufferedWriter = new SitemeshBufferWriter(128);
  56
+            exposedWriter = new SitemeshPrintWriter(bufferedWriter);
60 57
         }
61 58
         return exposedWriter;
62 59
     }
@@ -79,16 +76,4 @@ public void write(int b) {
79 76
     public boolean isUsingStream() {
80 77
         return bufferedStream != null;
81 78
     }
82  
-
83  
-    private static class AccessibleCharArrayWriter extends CharArrayWriter {
84  
-
85  
-        private AccessibleCharArrayWriter(int initialSize) {
86  
-            super(initialSize);
87  
-        }
88  
-
89  
-        public char[] getCharArray()
90  
-        {
91  
-            return buf;
92  
-        }
93  
-    }
94 79
 }
2  src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java
... ...
@@ -1,5 +1,7 @@
1 1
 package com.opensymphony.module.sitemesh.filter;
2 2
 
  3
+import java.util.TreeMap;
  4
+
3 5
 public class BufferedContent {
4 6
     private final char[] buffer;
5 7
     private final int length;
3  src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java
@@ -5,6 +5,7 @@
5 5
 
6 6
 import com.opensymphony.module.sitemesh.Page;
7 7
 import com.opensymphony.module.sitemesh.PageParserSelector;
  8
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
8 9
 
9 10
 import javax.servlet.ServletOutputStream;
10 11
 import javax.servlet.http.HttpServletResponse;
@@ -185,7 +186,7 @@ public boolean isUsingStream() {
185 186
         return buffer != null && buffer.isUsingStream();
186 187
     }
187 188
 
188  
-    public BufferedContent getContents() throws IOException {
  189
+    public SitemeshBuffer getContents() throws IOException {
189 190
         if (aborted || !parseablePage) {
190 191
             return null;
191 192
         } else {
26  src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java
@@ -7,6 +7,10 @@
7 7
 import java.io.IOException;
8 8
 import java.io.Writer;
9 9
 
  10
+import com.opensymphony.module.sitemesh.Page;
  11
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
  12
+import com.opensymphony.module.sitemesh.SitemeshWriter;
  13
+
10 14
 /**
11 15
  * Provides a PrintWriter that routes through to another PrintWriter, however the destination
12 16
  * can be changed at any point. The destination can be passed in using a factory, so it will not be created
@@ -15,7 +19,7 @@
15 19
  * @author Joe Walnes
16 20
  * @version $Revision: 1.1 $
17 21
  */
18  
-public class RoutablePrintWriter extends PrintWriter {
  22
+public class RoutablePrintWriter extends PrintWriter implements SitemeshWriter {
19 23
 
20 24
     private PrintWriter destination;
21 25
     private DestinationFactory factory;
@@ -179,4 +183,24 @@ public void close() throws IOException {
179 183
 
180 184
     }
181 185
 
  186
+    public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException
  187
+    {
  188
+        PrintWriter destination = getDestination();
  189
+        if (destination instanceof SitemeshWriter) {
  190
+            return ((SitemeshWriter) destination).writeSitemeshBuffer(sitemeshBuffer, start, length);
  191
+        } else {
  192
+            sitemeshBuffer.writeTo(destination, start, length);
  193
+            return true;
  194
+        }
  195
+    }
  196
+
  197
+    public SitemeshBuffer getSitemeshBuffer()
  198
+    {
  199
+        PrintWriter destination = getDestination();
  200
+        if (destination instanceof SitemeshWriter) {
  201
+            return ((SitemeshWriter) destination).getSitemeshBuffer();
  202
+        } else {
  203
+            throw new IllegalStateException("Print writer is not a sitemesh buffer");
  204
+        }
  205
+    }
182 206
 }
29  src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java
... ...
@@ -0,0 +1,29 @@
  1
+package com.opensymphony.module.sitemesh.filter;
  2
+
  3
+import com.opensymphony.module.sitemesh.SitemeshBufferWriter;
  4
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
  5
+import com.opensymphony.module.sitemesh.SitemeshWriter;
  6
+
  7
+import java.io.IOException;
  8
+import java.io.PrintWriter;
  9
+
  10
+/**
  11
+ * A sitemesh print writer
  12
+ */
  13
+public class SitemeshPrintWriter extends PrintWriter implements SitemeshWriter {
  14
+
  15
+    private final SitemeshWriter sitemeshWriter;
  16
+
  17
+    public SitemeshPrintWriter(SitemeshBufferWriter sitemeshWriter) {
  18
+        super(sitemeshWriter);
  19
+        this.sitemeshWriter = sitemeshWriter;
  20
+    }
  21
+
  22
+    public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException {
  23
+        return sitemeshWriter.writeSitemeshBuffer(sitemeshBuffer, start, length);
  24
+    }
  25
+
  26
+    public SitemeshBuffer getSitemeshBuffer() {
  27
+        return sitemeshWriter.getSitemeshBuffer();
  28
+    }
  29
+}
21  src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java
@@ -2,6 +2,7 @@
2 2
 
3 3
 import com.opensymphony.module.sitemesh.PageParser;
4 4
 import com.opensymphony.module.sitemesh.Page;
  5
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
5 6
 import com.opensymphony.module.sitemesh.html.util.CharArray;
6 7
 import com.opensymphony.module.sitemesh.html.HTMLProcessor;
7 8
 import com.opensymphony.module.sitemesh.html.BasicRule;
@@ -19,7 +20,23 @@ public MultipassReplacementPageParser(Page page, HttpServletResponse response) {
19 20
         this.response = response;
20 21
     }
21 22
 
22  
-    public Page parse(char[] data, int length) throws IOException
  23
+    public Page parse(SitemeshBuffer buffer) throws IOException {
  24
+        char[] data;
  25
+        int length;
  26
+        if (buffer.hasFragments()) {
  27
+            // Write the buffer into a char array
  28
+            com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength());
  29
+            buffer.writeTo(writer, 0, buffer.getBufferLength());
  30
+            data = writer.toCharArray();
  31
+            length = data.length;
  32
+        } else {
  33
+            data = buffer.getCharArray();
  34
+            length = buffer.getBufferLength();
  35
+        }
  36
+        return parse(data, length);
  37
+    }
  38
+
  39
+    private Page parse(char[] data, int length) throws IOException
23 40
     {
24 41
         if (data.length > length) {
25 42
             // todo fix this parser so that it doesn't need to compact the array
@@ -30,7 +47,7 @@ public Page parse(char[] data, int length) throws IOException
30 47
         return parse(data);
31 48
     }
32 49
     
33  
-    public Page parse(char[] data) throws IOException {
  50
+    private Page parse(char[] data) throws IOException {
34 51
         final CharArray result = new CharArray(4096);
35 52
         HTMLProcessor processor = new HTMLProcessor(data, result);
36 53
         processor.addRule(new BasicRule("sitemesh:multipass") {
20  src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java
@@ -11,8 +11,10 @@
11 11
 
12 12
 import com.opensymphony.module.sitemesh.Page;
13 13
 import com.opensymphony.module.sitemesh.PageParser;
  14
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
14 15
 import com.opensymphony.module.sitemesh.html.util.CharArray;
15 16
 import com.opensymphony.module.sitemesh.util.CharArrayReader;
  17
+import com.opensymphony.module.sitemesh.util.CharArrayWriter;
16 18
 
17 19
 import java.io.IOException;
18 20
 import java.io.Reader;
@@ -83,13 +85,21 @@
83 85
    private static final int SLASH_BODY_HASH = 46434897; // "/body".hashCode();
84 86
    private static final int CONTENT_HASH = 951530617; // "content".hashCode();
85 87
 
86  
-   public Page parse(char[] data) throws IOException
  88
+   public Page parse(SitemeshBuffer buffer) throws IOException
87 89
    {
88  
-       return parse(data, data.length);
89  
-   }
  90
+      char[] data;
  91
+      int length;
  92
+      if (buffer.hasFragments()) {
  93
+        // Write the buffer into a char array
  94
+        CharArrayWriter writer = new CharArrayWriter(buffer.getTotalLength());
  95
+        buffer.writeTo(writer, 0, buffer.getBufferLength());
  96
+        data = writer.toCharArray();
  97
+        length = data.length;
  98
+      } else {
  99
+        data = buffer.getCharArray();
  100
+        length = buffer.getBufferLength();
  101
+      }
90 102
 
91  
-   public Page parse(char[] data, int length) throws IOException
92  
-   {
93 103
       FastPage page = internalParse(new CharArrayReader(data, 0, length));
94 104
       page.setVerbatimPage(data, length);
95 105
       return page;
23  src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java
@@ -2,10 +2,10 @@
2 2
 
3 3
 import com.opensymphony.module.sitemesh.Page;
4 4
 import com.opensymphony.module.sitemesh.PageParser;
  5
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
5 6
 import com.opensymphony.module.sitemesh.html.HTMLProcessor;
6 7
 import com.opensymphony.module.sitemesh.html.State;
7 8
 import com.opensymphony.module.sitemesh.html.StateTransitionRule;
8  
-import com.opensymphony.module.sitemesh.html.tokenizer.TagTokenizer;
9 9
 import com.opensymphony.module.sitemesh.html.util.CharArray;
10 10
 import com.opensymphony.module.sitemesh.html.rules.BodyTagRule;
11 11
 import com.opensymphony.module.sitemesh.html.rules.ContentBlockExtractingRule;
@@ -18,7 +18,6 @@
18 18
 import com.opensymphony.module.sitemesh.html.rules.TitleExtractingRule;
19 19
 import com.opensymphony.module.sitemesh.html.rules.PageBuilder;
20 20
 
21  
-import java.io.CharArrayWriter;
22 21
 import java.io.IOException;
23 22
 
24 23
 /**
@@ -34,7 +33,23 @@
34 33
  */
35 34
 public class HTMLPageParser implements PageParser {
36 35
 
37  
-    public Page parse(char[] data, int length) throws IOException
  36
+    public Page parse(SitemeshBuffer buffer) throws IOException {
  37
+        char[] data;
  38
+        int length;
  39
+        if (buffer.hasFragments()) {
  40
+            // Write the buffer into a char array
  41
+            com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength());
  42
+            buffer.writeTo(writer, 0, buffer.getBufferLength());
  43
+            data = writer.toCharArray();
  44
+            length = data.length;
  45
+        } else {
  46
+            data = buffer.getCharArray();
  47
+            length = buffer.getBufferLength();
  48
+        }
  49
+        return parse(data, length);
  50
+    }
  51
+
  52
+    private Page parse(char[] data, int length) throws IOException
38 53
     {
39 54
         if (data.length > length) {
40 55
             // todo fix this parser so that it doesn't need to compact the array
@@ -45,7 +60,7 @@ public Page parse(char[] data, int length) throws IOException
45 60
         return parse(data);
46 61
     }
47 62
 
48  
-    public Page parse(char[] data) throws IOException {
  63
+    private Page parse(char[] data) throws IOException {
49 64
         CharArray head = new CharArray(64);
50 65
         CharArray body = new CharArray(4096);
51 66
         TokenizedHTMLPage page = new TokenizedHTMLPage(data, body, head);
12  src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java
... ...
@@ -1,6 +1,7 @@
1 1
 package com.opensymphony.module.sitemesh.parser;
2 2
 
3 3
 import com.opensymphony.module.sitemesh.HTMLPage;
  4
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
4 5
 
5 6
 import java.io.IOException;
6 7
 import java.io.Writer;
@@ -10,15 +11,14 @@
10 11
 {
11 12
     private final char[] head;
12 13
 
13  
-    public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map<String, String> bodyProperties)
  14
+    public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map<String, String> bodyProperties)
14 15
     {
15  
-        this(pageData, pageLength, bodyStart, bodyLength, bodyProperties, null, null, null, null);
  16
+        this(sitemeshBuffer, bodyStart, bodyLength, bodyProperties, null, null, null, null);
16 17
     }
17 18
 
18 19
     /**
19 20
      *
20  
-     * @param pageData The data for the page
21  
-     * @param pageLength The length of the page
  21
+     * @param sitemeshBuffer The buffer for the page
22 22
      * @param bodyStart The start of the body
23 23
      * @param bodyLength The length of the body
24 24
      * @param bodyProperties The properties of the body
@@ -27,10 +27,10 @@ public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bod
27 27
      * @param metaAttributes The meta attributes found in the head section
28 28
      * @param pageProperties The page properties extracted from the head section
29 29
      */
30  
-    public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map<String, String> bodyProperties,
  30
+    public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map<String, String> bodyProperties,
31 31
             char[] head, String title, Map<String, String> metaAttributes, Map<String, String> pageProperties)
32 32
     {
33  
-        super(pageData, pageLength, bodyStart, bodyLength);
  33
+        super(sitemeshBuffer, bodyStart, bodyLength);
34 34
         this.head = head;
35 35
         if (title == null)
36 36
         {
19  src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java
@@ -5,23 +5,26 @@
5 5
 import java.io.OutputStreamWriter;
6 6
 import java.io.Writer;
7 7
 
  8
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
  9
+import com.opensymphony.module.sitemesh.SitemeshWriter;
  10
+
8 11
 public class SuperFastPage extends AbstractPage {
9 12
 
  13
+    private final SitemeshBuffer sitemeshBuffer;
10 14
     private final int bodyStart;
11 15
     private final int bodyLength;
12  
-    private final int pageLength;
13 16
 
14 17
 
15  
-    public SuperFastPage(char[] pageData, int pageLength, int bodyStart, int bodyLength) {
16  
-        this.pageLength = pageLength;
  18
+    public SuperFastPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength) {
  19
+        this.sitemeshBuffer = sitemeshBuffer;
17 20
         this.bodyStart = bodyStart;
18 21
         this.bodyLength = bodyLength;
19  
-        this.pageData = pageData;
  22
+        this.pageData = sitemeshBuffer.getCharArray();
20 23
     }
21 24
 
22 25
     @Override
23 26
     public void writePage(Writer out) throws IOException {
24  
-        out.write(pageData, 0, pageLength);
  27
+        sitemeshBuffer.writeTo(out, 0, sitemeshBuffer.getBufferLength());
25 28
     }
26 29
 
27 30
     @Override
@@ -42,7 +45,11 @@ public int getContentLength() {
42 45
 
43 46
     @Override
44 47
     public void writeBody(Writer out) throws IOException {
45  
-        out.write(pageData, bodyStart, bodyLength);
  48
+        if (out instanceof SitemeshWriter) {
  49
+            ((SitemeshWriter) out).writeSitemeshBuffer(sitemeshBuffer, bodyStart, bodyLength);
  50
+        } else {
  51
+            sitemeshBuffer.writeTo(out, bodyStart, bodyLength);
  52
+        }
46 53
     }
47 54
 
48 55
     protected static class CountingOutputStream extends OutputStream {
24  src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java
@@ -7,6 +7,7 @@
7 7
 
8 8
 import com.opensymphony.module.sitemesh.Page;
9 9
 import com.opensymphony.module.sitemesh.PageParser;
  10
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
10 11
 
11 12
 /**
12 13
  * Super fast page parser.  It uses a single buffer, and never copies anything, apart from the title, meta attributes
@@ -19,13 +20,10 @@
19 20
  */
20 21
 public class SuperFastSimplePageParser implements PageParser
21 22
 {
22  
-    public Page parse(final char[] data) throws IOException
23  
-    {
24  
-        return parse(data, data.length);
25  
-    }
26  
-
27  
-    public Page parse(final char[] data, final int length) throws IOException
  23
+    public Page parse(SitemeshBuffer buffer) throws IOException
28 24
     {
  25
+        char[] data = buffer.getCharArray();
  26
+        int length = buffer.getBufferLength();
29 27
         int position = 0;
30 28
         while (position < data.length)
31 29
         {
@@ -39,21 +37,23 @@ public Page parse(final char[] data, final int length) throws IOException
39 37
                 if (compareLowerCase(data, length, position, "html"))
40 38
                 {
41 39
                     // It's an HTML page, handle HTML pages
42  
-                    return parseHtmlPage(data, length, position);
  40
+                    return parseHtmlPage(buffer, position);
43 41
                 }
44 42
                 else
45 43
                 {
46 44
                     // The whole thing is the body.
47  
-                    return new SuperFastHtmlPage(data, length, 0, length, null);
  45
+                    return new SuperFastHtmlPage(buffer, 0, length, null);
48 46
                 }
49 47
             }
50 48
         }
51 49
         // If we're here, we mustn't have found a tag
52  
-        return new SuperFastHtmlPage(data, length, 0, length, null);
  50
+        return new SuperFastHtmlPage(buffer, 0, length, null);
53 51
     }
54 52
 
55  
-    private Page parseHtmlPage(final char[] data, final int length, int position)
  53
+    private Page parseHtmlPage(SitemeshBuffer buffer, int position)
56 54
     {
  55
+        char[] data = buffer.getCharArray();
  56
+        int length = buffer.getBufferLength();
57 57
         int bodyStart = -1;
58 58
         int bodyLength = -1;
59 59
         int headStart = -1;
@@ -163,11 +163,11 @@ else if (compareLowerCase(data, headEnd, i + 1, "content"))
163 163
                 }
164 164
             }
165 165
 
166  
-            return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties);
  166
+            return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties);
167 167
         }
168 168
         else
169 169
         {
170  
-            return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties);
  170
+            return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties);
171 171
         }
172 172
     }
173 173
 
13  src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java
@@ -151,9 +151,12 @@ public int doEndTag() throws JspException {
151 151
             if (page == null) {
152 152
                 // inline content
153 153
                 if (bodyContent != null) {
154  
-                    pageObj = parser.parse(bodyContent.getString().toCharArray());
  154
+                    // Would be nice if we could do our own buffering...
  155
+                    SitemeshBufferWriter sitemeshWriter = new SitemeshBufferWriter();
  156
+                    bodyContent.writeOut(sitemeshWriter);
  157
+                    pageObj = parser.parse(sitemeshWriter.getSitemeshBuffer());
155 158
                 } else {
156  
-                    pageObj = parser.parse(new char[]{});
  159
+                    pageObj = parser.parse(new DefaultSitemeshBuffer(new char[] {}));
157 160
                 }
158 161
             }
159 162
             else if (page.startsWith("http://") || page.startsWith("https://")) {
@@ -164,15 +167,15 @@ else if (page.startsWith("http://") || page.startsWith("https://")) {
164 167
                     BufferedReader in = new BufferedReader(
165 168
                             new InputStreamReader(urlConn.getInputStream()));
166 169
 
167  
-                    StringBuffer sbuf = new StringBuffer();
  170
+                    SitemeshBufferWriter sitemeshWriter = new SitemeshBufferWriter();
168 171
                     char[] buf = new char[1000];
169 172
                     for (; ;) {
170 173
                         int moved = in.read(buf);
171 174
                         if (moved < 0) break;
172  
-                        sbuf.append(buf, 0, moved);
  175
+                        sitemeshWriter.write(buf, 0, moved);
173 176
                     }
174 177
                     in.close();
175  
-                    pageObj = parser.parse(sbuf.toString().toCharArray());
  178
+                    pageObj = parser.parse(sitemeshWriter.getSitemeshBuffer());
176 179
                 }
177 180
                 catch (MalformedURLException e) {
178 181
                     throw new JspException(e);
3  src/java/com/opensymphony/sitemesh/ContentProcessor.java
... ...
@@ -1,5 +1,6 @@
1 1
 package com.opensymphony.sitemesh;
2 2
 
  3
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
3 4
 import com.opensymphony.module.sitemesh.filter.BufferedContent;
4 5
 
5 6
 import java.io.IOException;
@@ -13,5 +14,5 @@
13 14
     boolean handles(SiteMeshContext context);
14 15
     boolean handles(String contentType);
15 16
 
16  
-    Content build(BufferedContent content, SiteMeshContext context) throws IOException;
  17
+    Content build(SitemeshBuffer buffer, SiteMeshContext context) throws IOException;
17 18
 }
9  src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java
... ...
@@ -1,9 +1,6 @@
1 1
 package com.opensymphony.sitemesh.compatability;
2 2
 
3  
-import com.opensymphony.module.sitemesh.Factory;
4  
-import com.opensymphony.module.sitemesh.HTMLPage;
5  
-import com.opensymphony.module.sitemesh.Page;
6  
-import com.opensymphony.module.sitemesh.PageParser;
  3
+import com.opensymphony.module.sitemesh.*;
7 4
 import com.opensymphony.module.sitemesh.filter.BufferedContent;
8 5
 import com.opensymphony.module.sitemesh.filter.HttpContentType;
9 6
 import com.opensymphony.sitemesh.Content;
@@ -46,10 +43,10 @@ public boolean handles(String contentType) {
46 43
         return factory.shouldParsePage(contentType);
47 44
     }
48 45
 
49  
-    public Content build(BufferedContent content, SiteMeshContext context) throws IOException {
  46
+    public Content build(SitemeshBuffer buffer, SiteMeshContext context) throws IOException {
50 47
         HttpContentType httpContentType = new HttpContentType(context.getContentType());
51 48
         PageParser pageParser = factory.getPageParser(httpContentType.getType());
52  
-        Page page = pageParser.parse(content.getBuffer(), content.getLength());
  49
+        Page page = pageParser.parse(buffer);
53 50
         return new HTMLPage2Content((HTMLPage) page);
54 51
     }
55 52
 }
3  src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java
... ...
@@ -1,5 +1,6 @@
1 1
 package com.opensymphony.sitemesh.webapp;
2 2
 
  3
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
3 4
 import com.opensymphony.module.sitemesh.filter.BufferedContent;
4 5
 import com.opensymphony.module.sitemesh.filter.PageResponseWrapper;
5 6
 import com.opensymphony.module.sitemesh.PageParserSelector;
@@ -50,7 +51,7 @@ public boolean isUsingStream() {
50 51
     }
51 52
 
52 53
     public Content getContent() throws IOException {
53  
-        BufferedContent content = pageResponseWrapper.getContents();
  54
+        SitemeshBuffer content = pageResponseWrapper.getContents();
54 55
         if (content != null) {
55 56
             return contentProcessor.build(content, webAppContext);
56 57
         } else {
61  src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java
... ...
@@ -0,0 +1,61 @@
  1
+package com.opensymphony.module.sitemesh.chaining;
  2
+
  3
+import java.io.CharArrayWriter;
  4
+import java.util.Arrays;
  5
+
  6
+import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
  7
+import com.opensymphony.module.sitemesh.SitemeshBuffer;
  8
+import com.opensymphony.module.sitemesh.SitemeshBufferFragment;
  9
+
  10
+import junit.framework.TestCase;
  11
+
  12
+/**
  13
+ */
  14
+public class ChainingBufferTest extends TestCase
  15
+{
  16
+    public void testSimpleChain() throws Exception
  17
+    {
  18
+        SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2));
  19
+        assertEquals("aabbaa", getContent(buffer));
  20
+    }
  21
+
  22
+    public void testBefore() throws Exception
  23
+    {
  24
+        SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2));
  25
+        assertEquals("a", getContent(buffer, 0, 1));
  26
+        assertEquals("aa", getContent(buffer, 0, 2));
  27
+    }
  28
+
  29
+    public void testAfter() throws Exception
  30
+    {
  31
+        SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2));
  32
+        assertEquals("bbaa", getContent(buffer, 2, 2));
  33
+        assertEquals("a", getContent(buffer, 3, 1));
  34
+    }
  35
+
  36
+    public void 
  37
+
  38
+    private String getContent(SitemeshBuffer buffer) throws Exception
  39
+    {
  40
+        CharArrayWriter writer = new CharArrayWriter();
  41
+        buffer.writeTo(writer, 0, buffer.getBufferLength());
  42
+        return writer.toString();
  43
+    }
  44
+
  45
+    private String getContent(SitemeshBuffer buffer, int start, int length) throws Exception
  46
+    {
  47
+        CharArrayWriter writer = new CharArrayWriter();
  48
+        buffer.writeTo(writer, start, length);
  49
+        return writer.toString();
  50
+    }
  51
+
  52
+    private SitemeshBuffer newSitemeshBuffer(String content, SitemeshBufferFragment... fragments)
  53
+    {
  54
+        return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), Arrays.asList(fragments));
  55
+    }
  56
+
  57
+    private SitemeshBufferFragment newBufferFragment(String content, int position)
  58
+    {
  59
+        return new SitemeshBufferFragment(newSitemeshBuffer(content), 0, content.length(), position);
  60
+    }
  61
+}
3  src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java
... ...
@@ -1,5 +1,6 @@
1 1
 package com.opensymphony.module.sitemesh.multipass;
2 2
 
  3
+import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
3 4
 import com.opensymphony.module.sitemesh.Page;
4 5
 import com.opensymphony.module.sitemesh.PageParser;
5 6
 import com.opensymphony.module.sitemesh.multipass.DivExtractingPageParser;
@@ -25,7 +26,7 @@ public void testReplacesTopLevelDivsWithPlaceHolders() throws IOException {
25 26
                 "</html>";
26 27
 
27 28
         PageParser parser = new DivExtractingPageParser();
28  
-        Page page = parser.parse(input.toCharArray());
  29
+        Page page = parser.parse(new DefaultSitemeshBuffer(input.toCharArray()));
29 30
 
30 31
         String expectedBody = "" +
31 32
                 "    <sitemesh:multipass id=\"div.one\"/>\n" +
5  src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java
... ...
@@ -1,5 +1,6 @@
1 1
 package com.opensymphony.module.sitemesh.parser;
2 2
 
  3
+import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
3 4
 import com.opensymphony.module.sitemesh.HTMLPage;
4 5
 import com.opensymphony.module.sitemesh.Page;
5 6
 import com.opensymphony.module.sitemesh.PageParser;
@@ -84,7 +85,7 @@ protected void setUp() throws Exception {
84 85
         this.blocks = readBlocks(new FileReader(file));
85 86
         // create PageParser and parse input block into HTMLPage object.
86 87
         String input = (String) blocks.get("INPUT");
87  
-        this.page = parser.parse(input.toCharArray());
  88
+        this.page = parser.parse(new DefaultSitemeshBuffer(input.toCharArray()));
88 89
     }
89 90
 
90 91
     public void testTitle() throws Exception {
@@ -146,7 +147,7 @@ public void testContentSanity() throws Exception {
146 147
         final char[] chars = input.toCharArray();
147 148
         final char[] bigChars = new char[chars.length * 2 + 10]; // make it bigger
148 149
         System.arraycopy(chars, 0, bigChars, 0, chars.length);
149  
-        Page bigPage = parser.parse(bigChars, chars.length);
  150
+        Page bigPage = parser.parse(new DefaultSitemeshBuffer(bigChars, chars.length));
150 151
 
151 152
         assertEquals(bigPage.getPage(), page.getPage());
152 153
         assertEquals(bigPage.getContentLength(), page.getContentLength());
9  src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java
... ...
@@ -1,5 +1,6 @@
1 1
 package com.opensymphony.module.sitemesh.parser;
2 2
 
  3
+import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer;
3 4
 import com.opensymphony.module.sitemesh.Page;
4 5
 import com.opensymphony.module.sitemesh.PageParser;
5 6
 
@@ -18,7 +19,7 @@
18 19
 {
19 20
 
20 21
     private static final String HTML_FILE = "sitemesh-performance-test.html";
21  
-    private static final String HTML_URL = "http://jira.atlassian.com/browse/JRA-1330";
  22
+    private static final String HTML_URL = "https://jira.atlassian.com/browse/JRA-1330";
22 23
     private static final int PARSE_COUNT = 1000;
23 24
 
24 25
     public static void main(String... args) throws Exception
@@ -82,9 +83,9 @@ public static void main(String... args) throws Exception
82 83
         System.gc();
83 84
         double normalTime = runPerformanceTest("Normal #3", page, normal, PARSE_COUNT);
84 85
         System.gc();
85  
-        double fastTime = runPerformanceTest("Fast #2", page, fast, PARSE_COUNT);
  86
+        double fastTime = runPerformanceTest("Fast #3", page, fast, PARSE_COUNT);
86 87
         System.gc();
87  
-        double superfastTime = runPerformanceTest("Super Fast #2", page, superfast, PARSE_COUNT);
  88
+        double superfastTime = runPerformanceTest("Super Fast #3", page, superfast, PARSE_COUNT);
88 89
 
89