From c53325f267b0d8958b8503439d69df8c9597d24d Mon Sep 17 00:00:00 2001 From: James Roper Date: Thu, 3 Feb 2011 11:23:16 +1100 Subject: [PATCH 01/20] Fixed tests, and modified performance test to run on any machine (not just mine :) --- src/parser-tests/superfast/ignore.txt | 0 .../parser/ParserPerformanceComparison.java | 131 ++++++++++++++---- 2 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 src/parser-tests/superfast/ignore.txt diff --git a/src/parser-tests/superfast/ignore.txt b/src/parser-tests/superfast/ignore.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java index 7be2e481..3f8bc827 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java @@ -4,52 +4,96 @@ import com.opensymphony.module.sitemesh.PageParser; import java.io.*; +import java.net.URL; -public class ParserPerformanceComparison { +/** + * This performance test compares the three HTML parsers performance. It downloads a large real world HTML file (at + * time of writing this file was 676KB and growing, this is a file that is generated using sitemesh), and then parses + * that with each parser 1000 times over. Between each parse it does a System.gc(), to ensure garbage left over from + * the last parser doesn't have to be collected during the next parsers run. And it runs each parser 3 times. This + * should ensure that by the third time, all major JIT has been done, and so the system will be as close to a running + * production server as possible. + */ +public class ParserPerformanceComparison +{ + + private static final String HTML_FILE = "sitemesh-performance-test.html"; + private static final String HTML_URL = "http://jira.atlassian.com/browse/JRA-1330"; + private static final int PARSE_COUNT = 1000; public static void main(String... args) throws Exception { - // Read the file + // Download the file if it doesn't exist + File file = new File(System.getProperty("java.io.tmpdir"), HTML_FILE); + if (!file.exists()) + { + System.out.println("Downloading " + HTML_URL + " to use for performance test"); + URL url = new URL(HTML_URL); + InputStream is = null; + OutputStream os = null; + try + { + is = url.openStream(); + os = new FileOutputStream(file); + copy(is, os); + } + finally + { + closeQuietly(is); + closeQuietly(os); + } + } + else + { + System.out.println("Using cached file " + file); + } + // Read the cached file into a buffer CharArrayWriter writer = new CharArrayWriter(); - BufferedReader reader = new BufferedReader(new FileReader("/home/jazzy/Desktop/test.html")); - char[] buffer = new char[4096]; - int length; - while ((length = reader.read(buffer)) > 0) + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(file)); + copy(reader, writer); + } + finally { - writer.write(buffer, 0, length); + closeQuietly(reader); } - reader.close(); + char[] page = writer.toCharArray(); // Create the parsers PageParser normal = new HTMLPageParser(); PageParser fast = new FastPageParser(); PageParser superfast = new SuperFastSimplePageParser(); - int times = 1000; - System.out.println("Amount of data: " + page.length); - System.gc(); - runPerformanceTest("Normal #1", page, normal, times); + runPerformanceTest("Normal #1", page, normal, PARSE_COUNT); System.gc(); - runPerformanceTest("Fast #1", page, fast, times); + runPerformanceTest("Fast #1", page, fast, PARSE_COUNT); System.gc(); - runPerformanceTest("Super Fast #1", page, superfast, times); + runPerformanceTest("Super Fast #1", page, superfast, PARSE_COUNT); System.gc(); - runPerformanceTest("Normal #2", page, normal, times); + runPerformanceTest("Normal #2", page, normal, PARSE_COUNT); System.gc(); - runPerformanceTest("Fast #2", page, fast, times); + runPerformanceTest("Fast #2", page, fast, PARSE_COUNT); System.gc(); - runPerformanceTest("Super Fast #2", page, superfast, times); + runPerformanceTest("Super Fast #2", page, superfast, PARSE_COUNT); System.gc(); - runPerformanceTest("Normal #3", page, normal, times); + double normalTime = runPerformanceTest("Normal #3", page, normal, PARSE_COUNT); System.gc(); - runPerformanceTest("Fast #2", page, fast, times); + double fastTime = runPerformanceTest("Fast #2", page, fast, PARSE_COUNT); System.gc(); - runPerformanceTest("Super Fast #2", page, superfast, times); + double superfastTime = runPerformanceTest("Super Fast #2", page, superfast, PARSE_COUNT); + + System.out.println("\nPerformance comparison %\n========================"); + System.out.println(String.format("%-10s%12s%12s%12s", "", "Normal", "Fast", "Super Fast")); + System.out.println(String.format("%-10s%12s% 11.1f%%% 11.1f%%", "Normal", "X", normalTime / fastTime * 100, normalTime / superfastTime * 100)); + System.out.println(String.format("%-10s% 11.1f%%%12s% 11.1f%%", "Fast", fastTime / normalTime * 100, "X", fastTime / superfastTime * 100)); + System.out.println(String.format("%-10s% 11.1f%%% 11.1f%%%12s", "Super Fast", superfastTime / normalTime * 100, superfastTime / fastTime * 100, "X")); } - public static void runPerformanceTest(String name, char[] data, PageParser parser, int times) throws Exception + public static long runPerformanceTest(String name, char[] data, PageParser parser, int times) throws Exception { Writer writer = new NullWriter(); long start = System.currentTimeMillis(); @@ -62,21 +106,60 @@ public static void runPerformanceTest(String name, char[] data, PageParser parse long time = finish - start; System.out.println(name + " total: " + time + "ms"); System.out.println(name + " average: " + (double) time / (double) times + "ms"); + return time; } private static class NullWriter extends Writer { @Override - public void write(char[] cbuf, int off, int len) throws IOException { + public void write(char[] cbuf, int off, int len) throws IOException + { // do nothnig } @Override - public void flush() throws IOException { + public void flush() throws IOException + { } @Override - public void close() throws IOException { + public void close() throws IOException + { + } + } + + private static void copy(InputStream is, OutputStream os) throws IOException + { + byte[] buf = new byte[4096]; + int length; + while ((length = is.read(buf)) > 0) + { + os.write(buf, 0, length); + } + } + + private static void copy(Reader reader, Writer writer) throws IOException + { + char[] buf = new char[4096]; + int length; + while ((length = reader.read(buf)) > 0) + { + writer.write(buf, 0, length); + } + } + + private static void closeQuietly(Closeable closeable) + { + try + { + if (closeable != null) + { + closeable.close(); + } + } + catch (IOException ioe) + { + // Ignore } } } From 298f85312b678027e9ca2be1bdd297cb4f136449 Mon Sep 17 00:00:00 2001 From: James Roper Date: Thu, 10 Feb 2011 13:21:06 +1100 Subject: [PATCH 02/20] Various fixes to make super fast page parser work in JIRA * Implemented body properties support * Implemented content tag support (only in head section) * A few trivial changes to ensure it behaved the same as the fast page parser * Fixed missed flush in getContentLength() * Added a few new tests to the superfast tests * Reenabled running tests on all parsers, and made the fast page parser run on the superfast tests --- src/parser-tests/superfast/test06.txt | 26 ++++++++++++++++++++ src/parser-tests/superfast/test07.txt | 35 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/parser-tests/superfast/test06.txt create mode 100644 src/parser-tests/superfast/test07.txt diff --git a/src/parser-tests/superfast/test06.txt b/src/parser-tests/superfast/test06.txt new file mode 100644 index 00000000..afa7cc0e --- /dev/null +++ b/src/parser-tests/superfast/test06.txt @@ -0,0 +1,26 @@ +~~~ INPUT ~~~ + + + + hello + + +

This is a pretty simple page.

+

Bye.

+ + + +~~~ TITLE ~~~ + +~~~ PROPERTIES ~~~ + +title= +page.blah=hello + +~~~ HEAD ~~~ + +~~~ BODY ~~~ + +

This is a pretty simple page.

+

Bye.

+ \ No newline at end of file diff --git a/src/parser-tests/superfast/test07.txt b/src/parser-tests/superfast/test07.txt new file mode 100644 index 00000000..94614846 --- /dev/null +++ b/src/parser-tests/superfast/test07.txt @@ -0,0 +1,35 @@ +~~~ INPUT ~~~ + + + + + +

Some markup

+
+ + +

This is a pretty simple page.

+

Bye.

+ + + +~~~ TITLE ~~~ + +~~~ PROPERTIES ~~~ + +title= +page.blah=

Some markup

+ +~~~ HEAD ~~~ + + + +~~~ BODY ~~~ + +

This is a pretty simple page.

+

Bye.

+ \ No newline at end of file From 147284a207f6db51c5df26248a93039c37144e93 Mon Sep 17 00:00:00 2001 From: James Roper Date: Thu, 10 Feb 2011 13:37:02 +1100 Subject: [PATCH 03/20] Various fixes to make super fast page parser work in JIRA * Implemented body properties support * Implemented content tag support (only in head section) * A few trivial changes to ensure it behaved the same as the fast page parser * Fixed missed flush in getContentLength() * Added a few new tests to the superfast tests * Reenabled running tests on all parsers, and made the fast page parser run on the superfast tests --- .../sitemesh/parser/AbstractHTMLPage.java | 9 +- .../sitemesh/parser/SuperFastHtmlPage.java | 72 ++++- .../module/sitemesh/parser/SuperFastPage.java | 8 +- .../parser/SuperFastSimplePageParser.java | 262 +++++++++++++++--- src/parser-tests/parsers.properties | 6 +- src/parser-tests/superfast/test01.txt | 1 + src/parser-tests/superfast/test02.txt | 2 + src/parser-tests/superfast/test03.txt | 3 + src/parser-tests/superfast/test04.txt | 2 + src/parser-tests/superfast/test05.txt | 5 +- 10 files changed, 311 insertions(+), 59 deletions(-) diff --git a/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java b/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java index 602662dc..6b5ee0c8 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java @@ -42,6 +42,13 @@ public boolean isFrameSet() { } public void setFrameSet(boolean frameset) { - addProperty("frameset", frameset ? "true" : "false"); + if (frameset) + { + addProperty("frameset", "true"); + } + else if (isPropertySet("frameset")) + { + addProperty("frameset", "false"); + } } } \ No newline at end of file diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java index 827e3674..f595433f 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java @@ -6,34 +6,80 @@ import java.io.Writer; import java.util.Map; -public class SuperFastHtmlPage extends SuperFastPage implements HTMLPage { - +public class SuperFastHtmlPage extends SuperFastPage implements HTMLPage +{ private final char[] head; - public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, char[] head, String title, Map metaAttributes) { + public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map bodyProperties) + { + this(pageData, pageLength, bodyStart, bodyLength, bodyProperties, null, null, null, null); + } + + /** + * + * @param pageData The data for the page + * @param pageLength The length of the page + * @param bodyStart The start of the body + * @param bodyLength The length of the body + * @param bodyProperties The properties of the body + * @param head The head section + * @param title The title + * @param metaAttributes The meta attributes found in the head section + * @param pageProperties The page properties extracted from the head section + */ + public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map bodyProperties, + char[] head, String title, Map metaAttributes, Map pageProperties) + { super(pageData, pageLength, bodyStart, bodyLength); this.head = head; - if (title != null) { - addProperty("title", title); + if (title == null) + { + title = ""; } - for (Map.Entry attribute : metaAttributes.entrySet()) { - addProperty("meta." + attribute.getKey(), attribute.getValue()); + addProperty("title", title); + addProperties(metaAttributes, "meta."); + addProperties(bodyProperties, "body."); + addProperties(pageProperties, "page."); + } + + private void addProperties(Map properties, String prefix) + { + if (properties != null) + { + for (Map.Entry property : properties.entrySet()) + { + addProperty(prefix + property.getKey(), property.getValue()); + } } } - public void writeHead(Writer out) throws IOException { - out.write(head); + public void writeHead(Writer out) throws IOException + { + if (head != null) + { + out.write(head); + } } - public String getHead() { - return new String(head); + public String getHead() + { + if (head != null) + { + return new String(head); + } + else + { + return ""; + } } - public boolean isFrameSet() { + public boolean isFrameSet() + { return false; } - public void setFrameSet(boolean frameset) { + public void setFrameSet(boolean frameset) + { throw new UnsupportedOperationException(); } } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java index 45aa4aa3..34a52522 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java @@ -28,8 +28,12 @@ public void writePage(Writer out) throws IOException { public int getContentLength() { // We encode it but not into a new buffer CountingOutputStream counter = new CountingOutputStream(); - try { - writePage(new OutputStreamWriter(counter)); + try + { + OutputStreamWriter writer = new OutputStreamWriter(counter); + writePage(writer); + // We mush flush, because the writer will buffer + writer.flush(); } catch (IOException ioe) { // Ignore, it's not possible with our OutputStream } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java index 55e51c78..415c509c 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java @@ -2,11 +2,8 @@ import java.io.CharArrayWriter; import java.io.IOException; -import java.nio.CharBuffer; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; @@ -16,21 +13,17 @@ * and body properties. This page parser makes several assumptions: *

*

  • If the first tag is an html tag, it's an HTML page, otherwise, it's a fragment, and no head/title/etc - * parsing will be done.
  • - *
+ * parsing will be done. * * @since v2.4 */ public class SuperFastSimplePageParser implements PageParser { - private static final Pattern META_PATTERN = Pattern.compile( - "", Pattern.CASE_INSENSITIVE); - public Page parse(final char[] data) throws IOException { return parse(data, data.length); } - + public Page parse(final char[] data, final int length) throws IOException { int position = 0; @@ -51,12 +44,12 @@ public Page parse(final char[] data, final int length) throws IOException else { // The whole thing is the body. - return new SuperFastPage(data, length, 0, length); + return new SuperFastHtmlPage(data, length, 0, length, null); } } } // If we're here, we mustn't have found a tag - return new SuperFastPage(data, length, 0, length); + return new SuperFastHtmlPage(data, length, 0, length, null); } private Page parseHtmlPage(final char[] data, final int length, int position) @@ -66,6 +59,7 @@ private Page parseHtmlPage(final char[] data, final int length, int position) int headStart = -1; int headLength = -1; // Find head end and start, and body start + Map bodyProperties = null; while (position < length) { if (data[position++] == '<') @@ -81,7 +75,9 @@ private Page parseHtmlPage(final char[] data, final int length, int position) } else if (compareLowerCase(data, length, position, "body")) { - bodyStart = findEndOf(data, length, position + 4, ">"); + HashSimpleMap map = new HashSimpleMap(); + bodyStart = parseProperties(data, length, position + 4, map); + bodyProperties = map.getMap(); break; } } @@ -111,37 +107,51 @@ else if (compareLowerCase(data, length, position, "body")) if (headLength > 0) { + int idx = headStart; + int headEnd = headStart + headLength; String title = null; - // Extract title and meta properties. This should be a small amount of data, so regexs are fine - CharBuffer buffer = CharBuffer.wrap(data, headStart, headLength); - Matcher matcher = META_PATTERN.matcher(buffer); + + // Extract meta attributes out of head Map metaAttributes = new HashMap(); - while (matcher.find()) + while (idx < headEnd) { - if (matcher.group(1).equals("content")) + if (data[idx++] == '<') { - metaAttributes.put(matcher.group(4), matcher.group(2)); - } - else - { - metaAttributes.put(matcher.group(2), matcher.group(4)); + if (compareLowerCase(data, headEnd, idx, "meta")) + { + MetaTagSimpleMap map = new MetaTagSimpleMap(); + idx = parseProperties(data, headEnd, idx + 4, map); + if (map.getName() != null && map.getContent() != null) + { + metaAttributes.put(map.getName(), map.getContent()); + } + } } } - // We need a new head buffer because we have to remove the title from it + // We need a new head buffer because we have to remove the title and content tags from it + Map pageProperties = new HashMap(); CharArrayWriter head = new CharArrayWriter(); - for (int i = headStart; i < headStart + headLength; i++) + for (int i = headStart; i < headEnd; i++) { char c = data[i]; if (c == '<') { - if (compareLowerCase(data, headLength, i + 1, "title")) + if (compareLowerCase(data, headEnd, i + 1, "title")) { - int titleStart = findEndOf(data, headLength, i + 6, ">"); - int titleEnd = findStartOf(data, headLength, titleStart, "<"); + int titleStart = findEndOf(data, headEnd, i + 6, ">"); + int titleEnd = findStartOf(data, headEnd, titleStart, "<"); title = new String(data, titleStart, titleEnd - titleStart); i = titleEnd + "".length() - 1; } + else if (compareLowerCase(data, headEnd, i + 1, "content")) + { + ContentTagSimpleMap map = new ContentTagSimpleMap(); + int contentStart = parseProperties(data, headEnd, i + 8, map); + int contentEnd = findStartOf(data, headEnd, contentStart, ""); + pageProperties.put(map.getTag(), new String(data, contentStart, contentEnd - contentStart)); + i = contentEnd + "".length() - 1; + } else { head.append(c); @@ -153,18 +163,18 @@ else if (compareLowerCase(data, length, position, "body")) } } - return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, head.toCharArray(), title, metaAttributes); + return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties); } else { - return new SuperFastPage(data, length, bodyStart, bodyLength); + return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties); } } - private static boolean compareLowerCase(final char[] data, final int length, int position, String token) + private static boolean compareLowerCase(final char[] data, final int dataEnd, int position, String token) { int l = position + token.length(); - if (l > length) + if (l > dataEnd) { return false; } @@ -181,27 +191,201 @@ private static boolean compareLowerCase(final char[] data, final int length, int return true; } - private static int findEndOf(final char[] data, final int length, int position, String token) + private static int findEndOf(final char[] data, final int dataEnd, int position, String token) { - for (int i = position; i < length - token.length(); i++) + for (int i = position; i < dataEnd - token.length(); i++) { - if (compareLowerCase(data, length, i, token)) + if (compareLowerCase(data, dataEnd, i, token)) { return i + token.length(); } } - return length; + return dataEnd; } - private static int findStartOf(final char[] data, final int length, int position, String token) + private static int findStartOf(final char[] data, final int dataEnd, int position, String token) { - for (int i = position; i < length - token.length(); i++) + for (int i = position; i < dataEnd - token.length(); i++) { - if (compareLowerCase(data, length, i, token)) + if (compareLowerCase(data, dataEnd, i, token)) { return i; } } - return length; + return dataEnd; + } + + /** + * Parse the properties of the current tag + * + * @param data the data + * @param dataEnd the end index of the data + * @param position our position in the data, this should be the first character after the tag name + * @param map to the map to parse the properties into + * + * @return The position of the first character after the tag + */ + private static int parseProperties(char[] data, int dataEnd, int position, SimpleMap map) + { + int idx = position; + + while (idx < dataEnd) + { + // Skip forward to the next non-whitespace character + while (idx < dataEnd && Character.isWhitespace(data[idx])) + { + idx++; + } + + // Make sure its not the end of the data or the end of the tag + if (idx == dataEnd || data[idx] == '>' || data[idx] == '/') + { + break; + } + + int startAttr = idx; + + // Find the next equals + while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '=' && data[idx] != '>') + { + idx++; + } + + if (idx == dataEnd || data[idx] != '=') + { + continue; + } + + String attrName = new String(data, startAttr, idx - startAttr); + + idx++; + if (idx == dataEnd) + { + break; + } + + int startValue = idx; + int endValue; + if (data[idx] == '"') + { + idx++; + startValue = idx; + while (idx < dataEnd && data[idx] != '"') + { + idx++; + } + if (idx == dataEnd) + { + break; + } + endValue = idx; + idx++; + } + else if (data[idx] == '\'') + { + idx++; + startValue = idx; + while (idx < dataEnd && data[idx] != '\'') + { + idx++; + } + if (idx == dataEnd) + { + break; + } + endValue = idx; + idx++; + } + else + { + while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '/' && data[idx] != '>') + { + idx++; + } + endValue = idx; + } + String attrValue = new String(data, startValue, endValue - startValue); + map.put(attrName, attrValue); + } + // Find the end of the tag + while (idx < dataEnd && data[idx] != '>') + { + idx++; + } + if (idx == dataEnd) + { + return idx; + } + else + { + // Return the first character after the end of the tag + return idx + 1; + } + } + + public static interface SimpleMap + { + public void put(String key, String value); + } + + public static class MetaTagSimpleMap implements SimpleMap + { + private String name; + private String content; + + public void put(String key, String value) + { + if (key.equals("name")) + { + name = value; + } + else if (key.equals("content")) + { + content = value; + } + } + + public String getName() + { + return name; + } + + public String getContent() + { + return content; + } + } + + public static class ContentTagSimpleMap implements SimpleMap + { + private String tag; + + public void put(String key, String value) + { + if (key.equals("tag")) + { + tag = value; + } + } + + public String getTag() + { + return tag; + } + } + + public static class HashSimpleMap implements SimpleMap + { + private final Map map = new HashMap(); + + public void put(String key, String value) + { + map.put(key, value); + } + + public Map getMap() + { + return map; + } } } diff --git a/src/parser-tests/parsers.properties b/src/parser-tests/parsers.properties index f0a1ce4d..05246765 100644 --- a/src/parser-tests/parsers.properties +++ b/src/parser-tests/parsers.properties @@ -1,6 +1,8 @@ -#parser.html.class=com.opensymphony.module.sitemesh.parser.HTMLPageParser +parser.html.class=com.opensymphony.module.sitemesh.parser.HTMLPageParser +parser.html.tests=src/parser-tests -#parser.fast.class=com.opensymphony.module.sitemesh.parser.FastPageParser +parser.fast.class=com.opensymphony.module.sitemesh.parser.FastPageParser +parser.fast.tests=src/parser-tests/superfast parser.superfast.class=com.opensymphony.module.sitemesh.parser.SuperFastSimplePageParser parser.superfast.tests=src/parser-tests/superfast \ No newline at end of file diff --git a/src/parser-tests/superfast/test01.txt b/src/parser-tests/superfast/test01.txt index b435ccc8..0494fd12 100644 --- a/src/parser-tests/superfast/test01.txt +++ b/src/parser-tests/superfast/test01.txt @@ -25,6 +25,7 @@ Some page title=Some page meta.author=Someone +body.bgcolor=black ~~~ HEAD ~~~ diff --git a/src/parser-tests/superfast/test02.txt b/src/parser-tests/superfast/test02.txt index 78ad8549..7d25d511 100644 --- a/src/parser-tests/superfast/test02.txt +++ b/src/parser-tests/superfast/test02.txt @@ -19,6 +19,8 @@ ~~~ PROPERTIES ~~~ +title= + ~~~ HEAD ~~~ @@ -22,10 +22,11 @@ Some page title=Some page meta.author=Someone +body.bgcolor=black ~~~ HEAD ~~~ - + From f8b0a6e97c96c6f068259a9888fe4b8d4b762681 Mon Sep 17 00:00:00 2001 From: Matt Quail Date: Mon, 6 Jun 2011 16:34:57 +1000 Subject: [PATCH 04/20] Fix bug (introduced by super-fast-parser work) where a rendering pipeline involving FastParser.parse(char[], int) would result in a content-length being a power of 2, and the trailing bytes all being 0. This fixes JRA-24760. --- .../module/sitemesh/parser/FastPage.java | 8 +++++++- .../module/sitemesh/parser/FastPageParser.java | 2 +- .../sitemesh/parser/HTMLPageParserTest.java | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java index 8ba0c4e9..1759663e 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java @@ -68,8 +68,14 @@ private void addAttributeList(String prefix, Map attributes) } } - public void setVerbatimPage(char[] v) + public void setVerbatimPage(char[] v, int length) { + if (v.length > length) { + // todo fix this parser so that it doesn't need to compact the array + char[] newData = new char[length]; + System.arraycopy(v, 0, newData, 0, length); + v = newData; + } this.pageData = v; } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java index 7432945c..c3d5ea2b 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java @@ -91,7 +91,7 @@ public Page parse(char[] data) throws IOException public Page parse(char[] data, int length) throws IOException { FastPage page = internalParse(new CharArrayReader(data, 0, length)); - page.setVerbatimPage(data); + page.setVerbatimPage(data, length); return page; } diff --git a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java index 20f610db..ec1a3a34 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java @@ -56,6 +56,7 @@ public static Test suite() throws Exception { suiteForFile.addTest(new HTMLPageParserTest(parser, file, "testHead")); suiteForFile.addTest(new HTMLPageParserTest(parser, file, "testFullPage")); suiteForFile.addTest(new HTMLPageParserTest(parser, file, "testProperties")); + suiteForFile.addTest(new HTMLPageParserTest(parser, file, "testContentSanity")); suiteForParser.addTest(suiteForFile); } result.addTest(suiteForParser); @@ -137,6 +138,20 @@ public void testProperties() throws Exception { } } + /** + * compare difference between using parse(char[]) and parse(char[], length) + */ + public void testContentSanity() throws Exception { + String input = (String) blocks.get("INPUT"); + final char[] chars = input.toCharArray(); + final char[] bigChars = new char[chars.length * 2 + 10]; // make it bigger + System.arraycopy(chars, 0, bigChars, 0, chars.length); + Page bigPage = parser.parse(bigChars, chars.length); + + assertEquals(bigPage.getPage(), page.getPage()); + assertEquals(bigPage.getContentLength(), page.getContentLength()); + } + private String join(String[] values) { StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { From e9baf699378c360dc2ad0f3258c4ebcb37aeddcf Mon Sep 17 00:00:00 2001 From: Matt Quail Date: Mon, 6 Jun 2011 16:39:34 +1000 Subject: [PATCH 05/20] make it easier to make atlassian releases of sitemesh --- build.xml | 8 ++++++++ pom.xml | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 pom.xml diff --git a/build.xml b/build.xml index 8977e70c..1ee3c5fc 100644 --- a/build.xml +++ b/build.xml @@ -123,6 +123,14 @@ + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..bfab675f --- /dev/null +++ b/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + + + opensymphony + sitemesh + 2.4.2-atlassianmlq-2 + + jar + SiteMesh + Atlassian's fork of SiteMesh + https://github.com/atlassian/sitemesh2 + + + + atlassian-m2-repository + Atlassian Public Repository + davs://maven.atlassian.com/public/ + + + atlassian-m2-snapshot-repository + Atlassian Public Snapshot Repository + davs://maven.atlassian.com/public-snapshot + + + + \ No newline at end of file From 292cc28efacacae6f6866e66d8ec0a5bf36c779f Mon Sep 17 00:00:00 2001 From: Matt Quail Date: Mon, 6 Jun 2011 16:56:21 +1000 Subject: [PATCH 06/20] changes for 2.4.2-atlassian-2 release --- pom.xml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index bfab675f..6fc7b137 100644 --- a/pom.xml +++ b/pom.xml @@ -5,31 +5,18 @@ opensymphony sitemesh - 2.4.2-atlassianmlq-2 + 2.4.2-atlassian-2 jar SiteMesh Atlassian's fork of SiteMesh https://github.com/atlassian/sitemesh2 - - - atlassian-m2-repository - Atlassian Public Repository - davs://maven.atlassian.com/public/ - - - atlassian-m2-snapshot-repository - Atlassian Public Snapshot Repository - davs://maven.atlassian.com/public-snapshot - - \ No newline at end of file From e50fec8396f093203e3de0372a064139e30e93cc Mon Sep 17 00:00:00 2001 From: James Roper Date: Sat, 2 Jul 2011 23:16:06 +1000 Subject: [PATCH 07/20] Added chained buffers. --- .gitignore | 4 + .../sitemesh/DefaultSitemeshBuffer.java | 85 +++++++++++++++++++ .../module/sitemesh/PageParser.java | 16 +--- .../module/sitemesh/SitemeshBuffer.java | 58 +++++++++++++ .../sitemesh/SitemeshBufferFragment.java | 38 +++++++++ .../module/sitemesh/SitemeshBufferWriter.java | 32 +++++++ .../module/sitemesh/SitemeshWriter.java | 28 ++++++ .../module/sitemesh/filter/Buffer.java | 33 ++----- .../sitemesh/filter/BufferedContent.java | 2 + .../sitemesh/filter/PageResponseWrapper.java | 3 +- .../sitemesh/filter/RoutablePrintWriter.java | 26 +++++- .../sitemesh/filter/SitemeshPrintWriter.java | 29 +++++++ .../MultipassReplacementPageParser.java | 21 ++++- .../sitemesh/parser/FastPageParser.java | 20 +++-- .../sitemesh/parser/HTMLPageParser.java | 23 ++++- .../sitemesh/parser/SuperFastHtmlPage.java | 12 +-- .../module/sitemesh/parser/SuperFastPage.java | 19 +++-- .../parser/SuperFastSimplePageParser.java | 24 +++--- .../taglib/page/ApplyDecoratorTag.java | 13 +-- .../sitemesh/ContentProcessor.java | 3 +- .../PageParser2ContentProcessor.java | 9 +- .../webapp/ContentBufferingResponse.java | 3 +- .../sitemesh/chaining/ChainingBufferTest.java | 61 +++++++++++++ .../DivExtractingPageParserTest.java | 3 +- .../sitemesh/parser/HTMLPageParserTest.java | 5 +- .../parser/ParserPerformanceComparison.java | 9 +- 26 files changed, 484 insertions(+), 95 deletions(-) create mode 100644 src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java create mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java create mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java create mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java create mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java create mode 100644 src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java create mode 100644 src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java diff --git a/.gitignore b/.gitignore index 9d0b71a3..b3da84d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ build +out +*.iml +*.ipr +*.iws dist diff --git a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java new file mode 100644 index 00000000..4d496cc9 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java @@ -0,0 +1,85 @@ +package com.opensymphony.module.sitemesh; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The default implementation of sitemesh buffer + */ +public class DefaultSitemeshBuffer implements SitemeshBuffer { + + private final char[] buffer; + private final int length; + private final List chainedBuffers; + + public DefaultSitemeshBuffer(char[] buffer) { + this(buffer, buffer.length); + } + + public DefaultSitemeshBuffer(char[] buffer, int length) { + this(buffer, length, Collections.emptyList()); + } + + public DefaultSitemeshBuffer(char[] buffer, int length, List chainedBuffers) { + this.buffer = buffer; + this.length = length; + this.chainedBuffers = new ArrayList(chainedBuffers); + Collections.sort(chainedBuffers); + } + + public void writeTo(Writer writer, int start, int length) throws IOException { + int pos = start; + for (SitemeshBufferFragment fragment : chainedBuffers) { + if (fragment.getPosition() < pos) { + continue; + } + if (fragment.getPosition() >= start + length) { + break; + } + // Write the buffer up to the fragment + writer.write(buffer, pos, fragment.getPosition() - pos); + // Write the fragment + fragment.getBuffer().writeTo(writer, fragment.getStart(), fragment.getLength()); + // increment pos + pos = fragment.getPosition(); + } + // Write out the remaining buffer + if (pos < start + length) { + writer.write(buffer, pos, (start + length) - pos); + } + } + + public int getTotalLength() { + return getTotalLength(0, length); + } + + public int getTotalLength(int start, int length) { + int total = length; + + for (SitemeshBufferFragment fragment : chainedBuffers) { + if (fragment.getPosition() < start) { + continue; + } + if (fragment.getPosition() > start + length) { + break; + } + total += fragment.getBuffer().getTotalLength(fragment.getStart(), fragment.getLength()); + } + return total; + } + + public int getBufferLength() { + return length; + } + + public char[] getCharArray() { + return buffer; + } + + public boolean hasFragments() { + return !chainedBuffers.isEmpty(); + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/PageParser.java b/src/java/com/opensymphony/module/sitemesh/PageParser.java index 55411e91..e201df65 100644 --- a/src/java/com/opensymphony/module/sitemesh/PageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/PageParser.java @@ -25,24 +25,12 @@ * @version $Revision: 1.2 $ */ public interface PageParser { - - /** - * This builds a Page. - * - * @param data The data for the page. Note, this array may be larger than the length of the content. - * @param length The length of the page. - * @return The parsed page - * @throws IOException if an error occurs - * @since 2.5 - */ - Page parse(char[] data, int length) throws IOException; - /** * This builds a Page. * - * @param data The data for the page. + * @param buffer The buffer for the page. * @return The parsed page * @throws IOException if an error occurs */ - Page parse(char[] data) throws IOException; + Page parse(SitemeshBuffer buffer) throws IOException; } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java new file mode 100644 index 00000000..942d7eb1 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBuffer.java @@ -0,0 +1,58 @@ +package com.opensymphony.module.sitemesh; + +import java.io.IOException; +import java.io.Writer; + +/** + * A potentially chained sitemesh buffer + */ +public interface SitemeshBuffer { + + /** + * Get the char array for this buffer. This array may be longer than the length of the content, you must use + * getBufferLength() in combination with this method. + * + * @return The char array for this buffer + */ + char[] getCharArray(); + + /** + * Get the length of the buffered content. + * + * @return The length of the buffered content. + */ + int getBufferLength(); + + /** + * Get the total length of the buffered content, including the length of any chained buffers. + * + * @return The total length. + */ + int getTotalLength(); + + /** + * Get the total length of the buffered content, including chained buffers from start to length + * + * @param start Where to start counting the length from + * @param length Where to finish + * @return THe total length in the given range + */ + int getTotalLength(int start, int length); + + /** + * Write this buffer, and any chained sub buffers in the given range, out to the given writer + * + * @param start The position to start writing from + * @param length The length to write + * @param writer The writer to write to + * @throws IOException If an error occurred + */ + void writeTo(Writer writer, int start, int length) throws IOException; + + /** + * Whether the buffer has fragments or not + * + * @return True if it has fragments + */ + boolean hasFragments(); +} diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java new file mode 100644 index 00000000..ed77dfc8 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java @@ -0,0 +1,38 @@ +package com.opensymphony.module.sitemesh; + +/** + * A fragment of a sitemesh buffer + */ +public class SitemeshBufferFragment implements Comparable { + private final SitemeshBuffer buffer; + private final int position; + private final int start; + private final int length; + + public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position) { + this.buffer = buffer; + this.start = start; + this.length = length; + this.position = position; + } + + public SitemeshBuffer getBuffer() { + return buffer; + } + + public int getStart() { + return start; + } + + public int getLength() { + return length; + } + + public int getPosition() { + return position; + } + + public int compareTo(SitemeshBufferFragment o) { + return o.position - position; + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java new file mode 100644 index 00000000..75108e95 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java @@ -0,0 +1,32 @@ +package com.opensymphony.module.sitemesh; + +import com.opensymphony.module.sitemesh.util.CharArrayWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A char array writer that caches other sitemesh buffers written to it, so that it doesn't have to continually copy + * them from buffer to buffer. + */ +public class SitemeshBufferWriter extends CharArrayWriter implements SitemeshWriter { + + private final List fragments = new ArrayList(); + + public SitemeshBufferWriter() { + } + + public SitemeshBufferWriter(int initialSize) { + super(initialSize); + } + + public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException { + fragments.add(new SitemeshBufferFragment(sitemeshBuffer, start, length, count)); + return false; + } + + public SitemeshBuffer getSitemeshBuffer() { + return new DefaultSitemeshBuffer(buf, count, fragments); + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java b/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java new file mode 100644 index 00000000..29264a49 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java @@ -0,0 +1,28 @@ +package com.opensymphony.module.sitemesh; + +import java.io.IOException; + +/** + * A sitemesh buffer writer. Provides the ability to defer the writing of a page body until it is finally written to the + * stream, so it isn't copied from buffer to buffer + */ +public interface SitemeshWriter { + /** + * Write a sitemesh buffer to the writer. This may not be written immediately, it may be stored and written later, + * when this buffer is written out to a writer. + * + * @param sitemeshBuffer The buffer to write + * @param start The place in the buffer to write from + * @param length The length of the buffer to write + * @return True if the buffer was written immediately, or false if it will be written later + * @throws IOException If an IOException occurred + */ + boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException; + + /** + * Get the underlying buffer for the writer + * + * @return The underlying buffer + */ + public SitemeshBuffer getSitemeshBuffer(); +} diff --git a/src/java/com/opensymphony/module/sitemesh/filter/Buffer.java b/src/java/com/opensymphony/module/sitemesh/filter/Buffer.java index 1532c354..a59992ba 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/Buffer.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/Buffer.java @@ -3,12 +3,10 @@ * distribution in the LICENSE.txt file. */ package com.opensymphony.module.sitemesh.filter; -import com.opensymphony.module.sitemesh.Page; -import com.opensymphony.module.sitemesh.PageParser; +import com.opensymphony.module.sitemesh.*; import com.opensymphony.module.sitemesh.util.FastByteArrayOutputStream; import javax.servlet.ServletOutputStream; -import java.io.CharArrayWriter; import java.io.IOException; import java.io.PrintWriter; @@ -25,7 +23,7 @@ public class Buffer { private final String encoding; private final static TextEncoder TEXT_ENCODER = new TextEncoder(); - private AccessibleCharArrayWriter bufferedWriter; + private SitemeshBufferWriter bufferedWriter; private FastByteArrayOutputStream bufferedStream; private PrintWriter exposedWriter; private ServletOutputStream exposedStream; @@ -35,19 +33,18 @@ public Buffer(PageParser pageParser, String encoding) { this.encoding = encoding; } - public BufferedContent getContents() throws IOException { + public SitemeshBuffer getContents() throws IOException { if (bufferedWriter != null) { - return new BufferedContent(bufferedWriter.getCharArray(), bufferedWriter.size()); + return bufferedWriter.getSitemeshBuffer(); } else if (bufferedStream != null) { - return new BufferedContent(TEXT_ENCODER.encode(bufferedStream.toByteArray(), encoding)); + return new DefaultSitemeshBuffer(TEXT_ENCODER.encode(bufferedStream.toByteArray(), encoding)); } else { - return new BufferedContent(new char[0]); + return new DefaultSitemeshBuffer(new char[0]); } } public Page parse() throws IOException { - BufferedContent content = getContents(); - return pageParser.parse(content.getBuffer(), content.getLength()); + return pageParser.parse(getContents()); } public PrintWriter getWriter() { @@ -55,8 +52,8 @@ public PrintWriter getWriter() { if (bufferedStream != null) { throw new IllegalStateException("response.getWriter() called after response.getOutputStream()"); } - bufferedWriter = new AccessibleCharArrayWriter(128); - exposedWriter = new PrintWriter(bufferedWriter); + bufferedWriter = new SitemeshBufferWriter(128); + exposedWriter = new SitemeshPrintWriter(bufferedWriter); } return exposedWriter; } @@ -79,16 +76,4 @@ public void write(int b) { public boolean isUsingStream() { return bufferedStream != null; } - - private static class AccessibleCharArrayWriter extends CharArrayWriter { - - private AccessibleCharArrayWriter(int initialSize) { - super(initialSize); - } - - public char[] getCharArray() - { - return buf; - } - } } diff --git a/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java b/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java index a89138eb..040ebaf8 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java @@ -1,5 +1,7 @@ package com.opensymphony.module.sitemesh.filter; +import java.util.TreeMap; + public class BufferedContent { private final char[] buffer; private final int length; diff --git a/src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java b/src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java index d1417f62..9ba0726a 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/PageResponseWrapper.java @@ -5,6 +5,7 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParserSelector; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; @@ -185,7 +186,7 @@ public boolean isUsingStream() { return buffer != null && buffer.isUsingStream(); } - public BufferedContent getContents() throws IOException { + public SitemeshBuffer getContents() throws IOException { if (aborted || !parseablePage) { return null; } else { diff --git a/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java b/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java index 31722f0a..acc9832c 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java @@ -7,6 +7,10 @@ import java.io.IOException; import java.io.Writer; +import com.opensymphony.module.sitemesh.Page; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshWriter; + /** * Provides a PrintWriter that routes through to another PrintWriter, however the destination * 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 @@ * @author Joe Walnes * @version $Revision: 1.1 $ */ -public class RoutablePrintWriter extends PrintWriter { +public class RoutablePrintWriter extends PrintWriter implements SitemeshWriter { private PrintWriter destination; private DestinationFactory factory; @@ -179,4 +183,24 @@ public void close() throws IOException { } + public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException + { + PrintWriter destination = getDestination(); + if (destination instanceof SitemeshWriter) { + return ((SitemeshWriter) destination).writeSitemeshBuffer(sitemeshBuffer, start, length); + } else { + sitemeshBuffer.writeTo(destination, start, length); + return true; + } + } + + public SitemeshBuffer getSitemeshBuffer() + { + PrintWriter destination = getDestination(); + if (destination instanceof SitemeshWriter) { + return ((SitemeshWriter) destination).getSitemeshBuffer(); + } else { + throw new IllegalStateException("Print writer is not a sitemesh buffer"); + } + } } diff --git a/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java b/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java new file mode 100644 index 00000000..e8b8e3ce --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java @@ -0,0 +1,29 @@ +package com.opensymphony.module.sitemesh.filter; + +import com.opensymphony.module.sitemesh.SitemeshBufferWriter; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshWriter; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * A sitemesh print writer + */ +public class SitemeshPrintWriter extends PrintWriter implements SitemeshWriter { + + private final SitemeshWriter sitemeshWriter; + + public SitemeshPrintWriter(SitemeshBufferWriter sitemeshWriter) { + super(sitemeshWriter); + this.sitemeshWriter = sitemeshWriter; + } + + public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException { + return sitemeshWriter.writeSitemeshBuffer(sitemeshBuffer, start, length); + } + + public SitemeshBuffer getSitemeshBuffer() { + return sitemeshWriter.getSitemeshBuffer(); + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java index 89277fc7..6d10bef9 100644 --- a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java @@ -2,6 +2,7 @@ import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.Page; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.html.HTMLProcessor; import com.opensymphony.module.sitemesh.html.BasicRule; @@ -19,7 +20,23 @@ public MultipassReplacementPageParser(Page page, HttpServletResponse response) { this.response = response; } - public Page parse(char[] data, int length) throws IOException + public Page parse(SitemeshBuffer buffer) throws IOException { + char[] data; + int length; + if (buffer.hasFragments()) { + // Write the buffer into a char array + com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength()); + buffer.writeTo(writer, 0, buffer.getBufferLength()); + data = writer.toCharArray(); + length = data.length; + } else { + data = buffer.getCharArray(); + length = buffer.getBufferLength(); + } + return parse(data, length); + } + + private Page parse(char[] data, int length) throws IOException { if (data.length > length) { // 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 return parse(data); } - public Page parse(char[] data) throws IOException { + private Page parse(char[] data) throws IOException { final CharArray result = new CharArray(4096); HTMLProcessor processor = new HTMLProcessor(data, result); processor.addRule(new BasicRule("sitemesh:multipass") { diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java index c3d5ea2b..6426e19e 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java @@ -11,8 +11,10 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.util.CharArrayReader; +import com.opensymphony.module.sitemesh.util.CharArrayWriter; import java.io.IOException; import java.io.Reader; @@ -83,13 +85,21 @@ public final class FastPageParser implements PageParser private static final int SLASH_BODY_HASH = 46434897; // "/body".hashCode(); private static final int CONTENT_HASH = 951530617; // "content".hashCode(); - public Page parse(char[] data) throws IOException + public Page parse(SitemeshBuffer buffer) throws IOException { - return parse(data, data.length); - } + char[] data; + int length; + if (buffer.hasFragments()) { + // Write the buffer into a char array + CharArrayWriter writer = new CharArrayWriter(buffer.getTotalLength()); + buffer.writeTo(writer, 0, buffer.getBufferLength()); + data = writer.toCharArray(); + length = data.length; + } else { + data = buffer.getCharArray(); + length = buffer.getBufferLength(); + } - public Page parse(char[] data, int length) throws IOException - { FastPage page = internalParse(new CharArrayReader(data, 0, length)); page.setVerbatimPage(data, length); return page; diff --git a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java index 097a6671..9c543ad5 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java @@ -2,10 +2,10 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.html.HTMLProcessor; import com.opensymphony.module.sitemesh.html.State; import com.opensymphony.module.sitemesh.html.StateTransitionRule; -import com.opensymphony.module.sitemesh.html.tokenizer.TagTokenizer; import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.html.rules.BodyTagRule; import com.opensymphony.module.sitemesh.html.rules.ContentBlockExtractingRule; @@ -18,7 +18,6 @@ import com.opensymphony.module.sitemesh.html.rules.TitleExtractingRule; import com.opensymphony.module.sitemesh.html.rules.PageBuilder; -import java.io.CharArrayWriter; import java.io.IOException; /** @@ -34,7 +33,23 @@ */ public class HTMLPageParser implements PageParser { - public Page parse(char[] data, int length) throws IOException + public Page parse(SitemeshBuffer buffer) throws IOException { + char[] data; + int length; + if (buffer.hasFragments()) { + // Write the buffer into a char array + com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength()); + buffer.writeTo(writer, 0, buffer.getBufferLength()); + data = writer.toCharArray(); + length = data.length; + } else { + data = buffer.getCharArray(); + length = buffer.getBufferLength(); + } + return parse(data, length); + } + + private Page parse(char[] data, int length) throws IOException { if (data.length > length) { // 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 return parse(data); } - public Page parse(char[] data) throws IOException { + private Page parse(char[] data) throws IOException { CharArray head = new CharArray(64); CharArray body = new CharArray(4096); TokenizedHTMLPage page = new TokenizedHTMLPage(data, body, head); diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java index f595433f..235eb07a 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java @@ -1,6 +1,7 @@ package com.opensymphony.module.sitemesh.parser; import com.opensymphony.module.sitemesh.HTMLPage; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import java.io.IOException; import java.io.Writer; @@ -10,15 +11,14 @@ public class SuperFastHtmlPage extends SuperFastPage implements HTMLPage { private final char[] head; - public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map bodyProperties) + public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map bodyProperties) { - this(pageData, pageLength, bodyStart, bodyLength, bodyProperties, null, null, null, null); + this(sitemeshBuffer, bodyStart, bodyLength, bodyProperties, null, null, null, null); } /** * - * @param pageData The data for the page - * @param pageLength The length of the page + * @param sitemeshBuffer The buffer for the page * @param bodyStart The start of the body * @param bodyLength The length of the body * @param bodyProperties The properties of the body @@ -27,10 +27,10 @@ public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bod * @param metaAttributes The meta attributes found in the head section * @param pageProperties The page properties extracted from the head section */ - public SuperFastHtmlPage(char[] pageData, int pageLength, int bodyStart, int bodyLength, Map bodyProperties, + public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map bodyProperties, char[] head, String title, Map metaAttributes, Map pageProperties) { - super(pageData, pageLength, bodyStart, bodyLength); + super(sitemeshBuffer, bodyStart, bodyLength); this.head = head; if (title == null) { diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java index 34a52522..09409b0c 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java @@ -5,23 +5,26 @@ import java.io.OutputStreamWriter; import java.io.Writer; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshWriter; + public class SuperFastPage extends AbstractPage { + private final SitemeshBuffer sitemeshBuffer; private final int bodyStart; private final int bodyLength; - private final int pageLength; - public SuperFastPage(char[] pageData, int pageLength, int bodyStart, int bodyLength) { - this.pageLength = pageLength; + public SuperFastPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength) { + this.sitemeshBuffer = sitemeshBuffer; this.bodyStart = bodyStart; this.bodyLength = bodyLength; - this.pageData = pageData; + this.pageData = sitemeshBuffer.getCharArray(); } @Override public void writePage(Writer out) throws IOException { - out.write(pageData, 0, pageLength); + sitemeshBuffer.writeTo(out, 0, sitemeshBuffer.getBufferLength()); } @Override @@ -42,7 +45,11 @@ public int getContentLength() { @Override public void writeBody(Writer out) throws IOException { - out.write(pageData, bodyStart, bodyLength); + if (out instanceof SitemeshWriter) { + ((SitemeshWriter) out).writeSitemeshBuffer(sitemeshBuffer, bodyStart, bodyLength); + } else { + sitemeshBuffer.writeTo(out, bodyStart, bodyLength); + } } protected static class CountingOutputStream extends OutputStream { diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java index 415c509c..f4916e8d 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java @@ -7,6 +7,7 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; +import com.opensymphony.module.sitemesh.SitemeshBuffer; /** * Super fast page parser. It uses a single buffer, and never copies anything, apart from the title, meta attributes @@ -19,13 +20,10 @@ */ public class SuperFastSimplePageParser implements PageParser { - public Page parse(final char[] data) throws IOException - { - return parse(data, data.length); - } - - public Page parse(final char[] data, final int length) throws IOException + public Page parse(SitemeshBuffer buffer) throws IOException { + char[] data = buffer.getCharArray(); + int length = buffer.getBufferLength(); int position = 0; while (position < data.length) { @@ -39,21 +37,23 @@ public Page parse(final char[] data, final int length) throws IOException if (compareLowerCase(data, length, position, "html")) { // It's an HTML page, handle HTML pages - return parseHtmlPage(data, length, position); + return parseHtmlPage(buffer, position); } else { // The whole thing is the body. - return new SuperFastHtmlPage(data, length, 0, length, null); + return new SuperFastHtmlPage(buffer, 0, length, null); } } } // If we're here, we mustn't have found a tag - return new SuperFastHtmlPage(data, length, 0, length, null); + return new SuperFastHtmlPage(buffer, 0, length, null); } - private Page parseHtmlPage(final char[] data, final int length, int position) + private Page parseHtmlPage(SitemeshBuffer buffer, int position) { + char[] data = buffer.getCharArray(); + int length = buffer.getBufferLength(); int bodyStart = -1; int bodyLength = -1; int headStart = -1; @@ -163,11 +163,11 @@ else if (compareLowerCase(data, headEnd, i + 1, "content")) } } - return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties); + return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties); } else { - return new SuperFastHtmlPage(data, length, bodyStart, bodyLength, bodyProperties); + return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties); } } diff --git a/src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java b/src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java index d601987c..7f45ed38 100644 --- a/src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java +++ b/src/java/com/opensymphony/module/sitemesh/taglib/page/ApplyDecoratorTag.java @@ -151,9 +151,12 @@ public int doEndTag() throws JspException { if (page == null) { // inline content if (bodyContent != null) { - pageObj = parser.parse(bodyContent.getString().toCharArray()); + // Would be nice if we could do our own buffering... + SitemeshBufferWriter sitemeshWriter = new SitemeshBufferWriter(); + bodyContent.writeOut(sitemeshWriter); + pageObj = parser.parse(sitemeshWriter.getSitemeshBuffer()); } else { - pageObj = parser.parse(new char[]{}); + pageObj = parser.parse(new DefaultSitemeshBuffer(new char[] {})); } } else if (page.startsWith("http://") || page.startsWith("https://")) { @@ -164,15 +167,15 @@ else if (page.startsWith("http://") || page.startsWith("https://")) { BufferedReader in = new BufferedReader( new InputStreamReader(urlConn.getInputStream())); - StringBuffer sbuf = new StringBuffer(); + SitemeshBufferWriter sitemeshWriter = new SitemeshBufferWriter(); char[] buf = new char[1000]; for (; ;) { int moved = in.read(buf); if (moved < 0) break; - sbuf.append(buf, 0, moved); + sitemeshWriter.write(buf, 0, moved); } in.close(); - pageObj = parser.parse(sbuf.toString().toCharArray()); + pageObj = parser.parse(sitemeshWriter.getSitemeshBuffer()); } catch (MalformedURLException e) { throw new JspException(e); diff --git a/src/java/com/opensymphony/sitemesh/ContentProcessor.java b/src/java/com/opensymphony/sitemesh/ContentProcessor.java index dd81f2f3..11317860 100644 --- a/src/java/com/opensymphony/sitemesh/ContentProcessor.java +++ b/src/java/com/opensymphony/sitemesh/ContentProcessor.java @@ -1,5 +1,6 @@ package com.opensymphony.sitemesh; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.filter.BufferedContent; import java.io.IOException; @@ -13,5 +14,5 @@ public interface ContentProcessor { boolean handles(SiteMeshContext context); boolean handles(String contentType); - Content build(BufferedContent content, SiteMeshContext context) throws IOException; + Content build(SitemeshBuffer buffer, SiteMeshContext context) throws IOException; } diff --git a/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java b/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java index cd831801..ad965517 100644 --- a/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java +++ b/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java @@ -1,9 +1,6 @@ package com.opensymphony.sitemesh.compatability; -import com.opensymphony.module.sitemesh.Factory; -import com.opensymphony.module.sitemesh.HTMLPage; -import com.opensymphony.module.sitemesh.Page; -import com.opensymphony.module.sitemesh.PageParser; +import com.opensymphony.module.sitemesh.*; import com.opensymphony.module.sitemesh.filter.BufferedContent; import com.opensymphony.module.sitemesh.filter.HttpContentType; import com.opensymphony.sitemesh.Content; @@ -46,10 +43,10 @@ public boolean handles(String contentType) { return factory.shouldParsePage(contentType); } - public Content build(BufferedContent content, SiteMeshContext context) throws IOException { + public Content build(SitemeshBuffer buffer, SiteMeshContext context) throws IOException { HttpContentType httpContentType = new HttpContentType(context.getContentType()); PageParser pageParser = factory.getPageParser(httpContentType.getType()); - Page page = pageParser.parse(content.getBuffer(), content.getLength()); + Page page = pageParser.parse(buffer); return new HTMLPage2Content((HTMLPage) page); } } diff --git a/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java b/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java index 33b8407a..0f16afaa 100644 --- a/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java +++ b/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java @@ -1,5 +1,6 @@ package com.opensymphony.sitemesh.webapp; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.filter.BufferedContent; import com.opensymphony.module.sitemesh.filter.PageResponseWrapper; import com.opensymphony.module.sitemesh.PageParserSelector; @@ -50,7 +51,7 @@ public boolean isUsingStream() { } public Content getContent() throws IOException { - BufferedContent content = pageResponseWrapper.getContents(); + SitemeshBuffer content = pageResponseWrapper.getContents(); if (content != null) { return contentProcessor.build(content, webAppContext); } else { diff --git a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java new file mode 100644 index 00000000..d5c946ce --- /dev/null +++ b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java @@ -0,0 +1,61 @@ +package com.opensymphony.module.sitemesh.chaining; + +import java.io.CharArrayWriter; +import java.util.Arrays; + +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; + +import junit.framework.TestCase; + +/** + */ +public class ChainingBufferTest extends TestCase +{ + public void testSimpleChain() throws Exception + { + SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); + assertEquals("aabbaa", getContent(buffer)); + } + + public void testBefore() throws Exception + { + SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); + assertEquals("a", getContent(buffer, 0, 1)); + assertEquals("aa", getContent(buffer, 0, 2)); + } + + public void testAfter() throws Exception + { + SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); + assertEquals("bbaa", getContent(buffer, 2, 2)); + assertEquals("a", getContent(buffer, 3, 1)); + } + + public void + + private String getContent(SitemeshBuffer buffer) throws Exception + { + CharArrayWriter writer = new CharArrayWriter(); + buffer.writeTo(writer, 0, buffer.getBufferLength()); + return writer.toString(); + } + + private String getContent(SitemeshBuffer buffer, int start, int length) throws Exception + { + CharArrayWriter writer = new CharArrayWriter(); + buffer.writeTo(writer, start, length); + return writer.toString(); + } + + private SitemeshBuffer newSitemeshBuffer(String content, SitemeshBufferFragment... fragments) + { + return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), Arrays.asList(fragments)); + } + + private SitemeshBufferFragment newBufferFragment(String content, int position) + { + return new SitemeshBufferFragment(newSitemeshBuffer(content), 0, content.length(), position); + } +} diff --git a/src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java b/src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java index 37647cfc..847effd1 100644 --- a/src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java +++ b/src/test/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParserTest.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.multipass; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.multipass.DivExtractingPageParser; @@ -25,7 +26,7 @@ public void testReplacesTopLevelDivsWithPlaceHolders() throws IOException { ""; PageParser parser = new DivExtractingPageParser(); - Page page = parser.parse(input.toCharArray()); + Page page = parser.parse(new DefaultSitemeshBuffer(input.toCharArray())); String expectedBody = "" + " \n" + diff --git a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java index ec1a3a34..703adf09 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.HTMLPage; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; @@ -84,7 +85,7 @@ protected void setUp() throws Exception { this.blocks = readBlocks(new FileReader(file)); // create PageParser and parse input block into HTMLPage object. String input = (String) blocks.get("INPUT"); - this.page = parser.parse(input.toCharArray()); + this.page = parser.parse(new DefaultSitemeshBuffer(input.toCharArray())); } public void testTitle() throws Exception { @@ -146,7 +147,7 @@ public void testContentSanity() throws Exception { final char[] chars = input.toCharArray(); final char[] bigChars = new char[chars.length * 2 + 10]; // make it bigger System.arraycopy(chars, 0, bigChars, 0, chars.length); - Page bigPage = parser.parse(bigChars, chars.length); + Page bigPage = parser.parse(new DefaultSitemeshBuffer(bigChars, chars.length)); assertEquals(bigPage.getPage(), page.getPage()); assertEquals(bigPage.getContentLength(), page.getContentLength()); diff --git a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java index 3f8bc827..aa9796f6 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; @@ -18,7 +19,7 @@ public class ParserPerformanceComparison { private static final String HTML_FILE = "sitemesh-performance-test.html"; - private static final String HTML_URL = "http://jira.atlassian.com/browse/JRA-1330"; + private static final String HTML_URL = "https://jira.atlassian.com/browse/JRA-1330"; private static final int PARSE_COUNT = 1000; public static void main(String... args) throws Exception @@ -82,9 +83,9 @@ public static void main(String... args) throws Exception System.gc(); double normalTime = runPerformanceTest("Normal #3", page, normal, PARSE_COUNT); System.gc(); - double fastTime = runPerformanceTest("Fast #2", page, fast, PARSE_COUNT); + double fastTime = runPerformanceTest("Fast #3", page, fast, PARSE_COUNT); System.gc(); - double superfastTime = runPerformanceTest("Super Fast #2", page, superfast, PARSE_COUNT); + double superfastTime = runPerformanceTest("Super Fast #3", page, superfast, PARSE_COUNT); System.out.println("\nPerformance comparison %\n========================"); System.out.println(String.format("%-10s%12s%12s%12s", "", "Normal", "Fast", "Super Fast")); @@ -99,7 +100,7 @@ public static long runPerformanceTest(String name, char[] data, PageParser parse long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { - Page page = parser.parse(data); + Page page = parser.parse(new DefaultSitemeshBuffer(data)); page.writeBody(writer); } long finish = System.currentTimeMillis(); From 10c0fb0c1339a03edff6cbbf3392c6e1cab67176 Mon Sep 17 00:00:00 2001 From: James Roper Date: Sat, 2 Jul 2011 23:44:36 +1000 Subject: [PATCH 08/20] Finished unit tests for chained buffers --- .../sitemesh/DefaultSitemeshBuffer.java | 14 ++-- .../sitemesh/chaining/ChainingBufferTest.java | 73 ++++++++++++------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java index 4d496cc9..ba628681 100644 --- a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java +++ b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java @@ -13,7 +13,7 @@ public class DefaultSitemeshBuffer implements SitemeshBuffer { private final char[] buffer; private final int length; - private final List chainedBuffers; + private final List bufferFragments; public DefaultSitemeshBuffer(char[] buffer) { this(buffer, buffer.length); @@ -23,16 +23,16 @@ public DefaultSitemeshBuffer(char[] buffer, int length) { this(buffer, length, Collections.emptyList()); } - public DefaultSitemeshBuffer(char[] buffer, int length, List chainedBuffers) { + public DefaultSitemeshBuffer(char[] buffer, int length, List bufferFragments) { this.buffer = buffer; this.length = length; - this.chainedBuffers = new ArrayList(chainedBuffers); - Collections.sort(chainedBuffers); + this.bufferFragments = new ArrayList(bufferFragments); + Collections.sort(bufferFragments); } public void writeTo(Writer writer, int start, int length) throws IOException { int pos = start; - for (SitemeshBufferFragment fragment : chainedBuffers) { + for (SitemeshBufferFragment fragment : bufferFragments) { if (fragment.getPosition() < pos) { continue; } @@ -59,7 +59,7 @@ public int getTotalLength() { public int getTotalLength(int start, int length) { int total = length; - for (SitemeshBufferFragment fragment : chainedBuffers) { + for (SitemeshBufferFragment fragment : bufferFragments) { if (fragment.getPosition() < start) { continue; } @@ -80,6 +80,6 @@ public char[] getCharArray() { } public boolean hasFragments() { - return !chainedBuffers.isEmpty(); + return !bufferFragments.isEmpty(); } } diff --git a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java index d5c946ce..55ec310f 100644 --- a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java +++ b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java @@ -7,55 +7,78 @@ import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.SitemeshBufferFragment; +import com.opensymphony.module.sitemesh.SitemeshBufferWriter; import junit.framework.TestCase; /** */ -public class ChainingBufferTest extends TestCase -{ - public void testSimpleChain() throws Exception - { - SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); - assertEquals("aabbaa", getContent(buffer)); +public class ChainingBufferTest extends TestCase { + public void testSimpleChain() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + assertEquals("12ab34", getContent(buffer)); } - public void testBefore() throws Exception - { - SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); - assertEquals("a", getContent(buffer, 0, 1)); - assertEquals("aa", getContent(buffer, 0, 2)); + public void testBefore() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + assertEquals("1", getContent(buffer, 0, 1)); + assertEquals("12", getContent(buffer, 0, 2)); } - public void testAfter() throws Exception - { - SitemeshBuffer buffer = newSitemeshBuffer("aaaa", newBufferFragment("bb", 2)); - assertEquals("bbaa", getContent(buffer, 2, 2)); - assertEquals("a", getContent(buffer, 3, 1)); + public void testAfter() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + assertEquals("ab34", getContent(buffer, 2, 2)); + assertEquals("4", getContent(buffer, 3, 1)); } - public void + public void testFragment() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("abcd", 1, 2, 2)); + assertEquals("12bc34", getContent(buffer)); + } + + public void testDeepFragments() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("123456789", + newBufferFragment("abcdefg", 3, + newBufferFragment("hijklm", 1, 1, 4), + newBufferFragment("nopqr", 1, 4, 5)), + newBufferFragment("tuzwx", 0, 2, 8)); + assertEquals("123abcdieopqrfg45678tu9", getContent(buffer)); + } + + public void testWriter() throws Exception { + SitemeshBuffer buffer = newSitemeshBuffer("123456"); + SitemeshBufferWriter writer = new SitemeshBufferWriter(); + writer.write("abc"); + writer.writeSitemeshBuffer(buffer, 1, 4); + writer.write("def"); + assertEquals("abcdef", writer.toString()); + assertEquals("abc2345def", getContent(writer.getSitemeshBuffer())); + } - private String getContent(SitemeshBuffer buffer) throws Exception - { + private String getContent(SitemeshBuffer buffer) throws Exception { CharArrayWriter writer = new CharArrayWriter(); buffer.writeTo(writer, 0, buffer.getBufferLength()); return writer.toString(); } - private String getContent(SitemeshBuffer buffer, int start, int length) throws Exception - { + private String getContent(SitemeshBuffer buffer, int start, int length) throws Exception { CharArrayWriter writer = new CharArrayWriter(); buffer.writeTo(writer, start, length); return writer.toString(); } - private SitemeshBuffer newSitemeshBuffer(String content, SitemeshBufferFragment... fragments) - { + private SitemeshBuffer newSitemeshBuffer(String content, SitemeshBufferFragment... fragments) { return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), Arrays.asList(fragments)); } - private SitemeshBufferFragment newBufferFragment(String content, int position) - { + private SitemeshBufferFragment newBufferFragment(String content, int position) { return new SitemeshBufferFragment(newSitemeshBuffer(content), 0, content.length(), position); } + + private SitemeshBufferFragment newBufferFragment(String content, int start, int length, int position) { + return new SitemeshBufferFragment(newSitemeshBuffer(content), start, length, position); + } + + private SitemeshBufferFragment newBufferFragment(String content, int position, SitemeshBufferFragment... fragments) { + return new SitemeshBufferFragment(newSitemeshBuffer(content, fragments), 0, content.length(), position); + } } From 7e8206e611ea941ddebb67994127b7ef64de80fa Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 5 Jul 2011 13:50:37 +1000 Subject: [PATCH 09/20] Added support for buffer deletions --- .../sitemesh/DefaultSitemeshBuffer.java | 4 +- .../sitemesh/SitemeshBufferFragment.java | 45 ++++++++++++++++++- .../SitemeshBufferFragmentDeletion.java | 26 +++++++++++ .../sitemesh/chaining/ChainingBufferTest.java | 2 - 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java diff --git a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java index 4d496cc9..755e5b3a 100644 --- a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java +++ b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java @@ -42,7 +42,7 @@ public void writeTo(Writer writer, int start, int length) throws IOException { // Write the buffer up to the fragment writer.write(buffer, pos, fragment.getPosition() - pos); // Write the fragment - fragment.getBuffer().writeTo(writer, fragment.getStart(), fragment.getLength()); + fragment.writeTo(writer); // increment pos pos = fragment.getPosition(); } @@ -66,7 +66,7 @@ public int getTotalLength(int start, int length) { if (fragment.getPosition() > start + length) { break; } - total += fragment.getBuffer().getTotalLength(fragment.getStart(), fragment.getLength()); + total += fragment.getTotalLength(); } return total; } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java index ed77dfc8..feae7dbe 100644 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java @@ -1,5 +1,10 @@ package com.opensymphony.module.sitemesh; +import java.io.IOException; +import java.io.Writer; +import java.util.Collections; +import java.util.List; + /** * A fragment of a sitemesh buffer */ @@ -8,16 +13,52 @@ public class SitemeshBufferFragment implements Comparable deletions; public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position) { + this(buffer, start, length, position, Collections.emptyList()); + } + + public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position, List deletions) { this.buffer = buffer; this.start = start; this.length = length; this.position = position; + this.deletions = deletions; + } + + public void writeTo(Writer writer) throws IOException + { + int pos = start; + for (SitemeshBufferFragmentDeletion deletion : deletions) { + if (deletion.getStart() > pos) { + buffer.writeTo(writer, pos, deletion.getStart() - pos); + } + pos = deletion.getStart() + deletion.getLength(); + } + int remain = start + length - pos; + if (remain > 0) + { + buffer.writeTo(writer, pos, remain); + } } - public SitemeshBuffer getBuffer() { - return buffer; + public int getTotalLength() + { + int total = 0; + int pos = start; + for (SitemeshBufferFragmentDeletion deletion : deletions) { + if (deletion.getStart() > pos) { + total += buffer.getTotalLength(pos, deletion.getStart() - pos); + } + pos = deletion.getStart() + deletion.getLength(); + } + int remain = start + length - pos; + if (remain > 0) + { + total += remain; + } + return total; } public int getStart() { diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java new file mode 100644 index 00000000..6eb1fb05 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java @@ -0,0 +1,26 @@ +package com.opensymphony.module.sitemesh; + +/** + * + */ +public class SitemeshBufferFragmentDeletion +{ + private final int start; + private final int length; + + public SitemeshBufferFragmentDeletion(int start, int length) + { + this.start = start; + this.length = length; + } + + public int getStart() + { + return start; + } + + public int getLength() + { + return length; + } +} diff --git a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java index d5c946ce..5edbba97 100644 --- a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java +++ b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java @@ -33,8 +33,6 @@ public void testAfter() throws Exception assertEquals("a", getContent(buffer, 3, 1)); } - public void - private String getContent(SitemeshBuffer buffer) throws Exception { CharArrayWriter writer = new CharArrayWriter(); From ae5c55cc64d7a93612ef81a9ea5ccefac9e17c3e Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 5 Jul 2011 18:37:52 +1000 Subject: [PATCH 10/20] Converted HTMLPageParser to use a single buffer --- .../sitemesh/DefaultSitemeshBuffer.java | 78 +++- .../sitemesh/SitemeshBufferFragment.java | 236 +++++++++-- .../SitemeshBufferFragmentDeletion.java | 26 -- .../module/sitemesh/SitemeshBufferWriter.java | 9 +- .../module/sitemesh/SitemeshWriter.java | 10 +- .../sitemesh/filter/BufferedContent.java | 26 -- .../sitemesh/filter/RoutablePrintWriter.java | 7 +- .../sitemesh/filter/SitemeshPrintWriter.java | 5 +- .../module/sitemesh/html/BasicRule.java | 10 +- .../sitemesh/html/BlockExtractingRule.java | 30 +- .../module/sitemesh/html/CustomTag.java | 25 +- .../module/sitemesh/html/HTMLProcessor.java | 60 +-- .../sitemesh/html/HTMLProcessorContext.java | 11 +- .../module/sitemesh/html/State.java | 15 +- .../sitemesh/html/StateTransitionRule.java | 3 - .../module/sitemesh/html/Tag.java | 15 +- .../module/sitemesh/html/Text.java | 16 +- .../sitemesh/html/rules/BodyTagRule.java | 11 +- .../rules/ContentBlockExtractingRule.java | 2 +- .../sitemesh/html/rules/FramesetRule.java | 1 + .../html/rules/HeadExtractingRule.java | 8 +- .../html/rules/HtmlAttributesRule.java | 3 + .../rules/MSOfficeDocumentPropertiesRule.java | 4 +- .../sitemesh/html/rules/MetaTagRule.java | 1 - .../html/rules/ParameterExtractingRule.java | 1 + .../sitemesh/html/rules/TagReplaceRule.java | 3 +- .../html/rules/TitleExtractingRule.java | 2 +- .../sitemesh/html/tokenizer/Parser.java | 18 +- .../sitemesh/html/tokenizer/TagTokenizer.java | 8 +- .../html/util/StringSitemeshBuffer.java | 48 +++ .../multipass/DivExtractingPageParser.java | 15 +- .../MultipassReplacementPageParser.java | 39 +- .../sitemesh/parser/AbstractHTMLPage.java | 5 + .../module/sitemesh/parser/AbstractPage.java | 45 +- .../module/sitemesh/parser/FastPage.java | 16 +- .../sitemesh/parser/FastPageParser.java | 28 +- .../sitemesh/parser/HTMLPageParser.java | 38 +- .../sitemesh/parser/PartialPageParser.java | 381 +++++++++++++++++ ...ge.java => PartialPageParserHtmlPage.java} | 29 +- .../parser/PartialPageParserPage.java | 29 ++ .../module/sitemesh/parser/SuperFastPage.java | 67 --- .../parser/SuperFastSimplePageParser.java | 389 +----------------- .../sitemesh/parser/TokenizedHTMLPage.java | 36 +- src/parser-tests/parsers.properties | 2 +- src/parser-tests/test10.txt | 3 - .../sitemesh/chaining/ChainingBufferTest.java | 61 ++- .../sitemesh/html/HTMLProcessorTest.java | 63 ++- .../rules/RegexReplacementTextFilterTest.java | 25 +- .../parser/ParserPerformanceComparison.java | 2 +- 49 files changed, 1086 insertions(+), 879 deletions(-) delete mode 100644 src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java delete mode 100644 src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java create mode 100644 src/java/com/opensymphony/module/sitemesh/html/util/StringSitemeshBuffer.java create mode 100644 src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java rename src/java/com/opensymphony/module/sitemesh/parser/{SuperFastHtmlPage.java => PartialPageParserHtmlPage.java} (59%) create mode 100644 src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserPage.java delete mode 100644 src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java diff --git a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java index ba77b6d5..3edae4be 100644 --- a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java +++ b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java @@ -2,9 +2,7 @@ import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; /** * The default implementation of sitemesh buffer @@ -13,38 +11,38 @@ public class DefaultSitemeshBuffer implements SitemeshBuffer { private final char[] buffer; private final int length; - private final List bufferFragments; + private final TreeMap bufferFragments; public DefaultSitemeshBuffer(char[] buffer) { this(buffer, buffer.length); } public DefaultSitemeshBuffer(char[] buffer, int length) { - this(buffer, length, Collections.emptyList()); + this(buffer, length, new TreeMap()); } - public DefaultSitemeshBuffer(char[] buffer, int length, List bufferFragments) { + public DefaultSitemeshBuffer(char[] buffer, int length, TreeMap bufferFragments) { this.buffer = buffer; this.length = length; - this.bufferFragments = new ArrayList(bufferFragments); - Collections.sort(bufferFragments); + this.bufferFragments = bufferFragments; } public void writeTo(Writer writer, int start, int length) throws IOException { int pos = start; - for (SitemeshBufferFragment fragment : bufferFragments) { - if (fragment.getPosition() < pos) { + for (Map.Entry entry : bufferFragments.entrySet()) { + int fragmentPosition = entry.getKey(); + if (fragmentPosition < pos) { continue; } - if (fragment.getPosition() >= start + length) { + if (fragmentPosition > start + length) { break; } // Write the buffer up to the fragment - writer.write(buffer, pos, fragment.getPosition() - pos); + writer.write(buffer, pos, fragmentPosition - pos); // Write the fragment - fragment.writeTo(writer); + entry.getValue().writeTo(writer); // increment pos - pos = fragment.getPosition(); + pos = fragmentPosition; } // Write out the remaining buffer if (pos < start + length) { @@ -59,14 +57,15 @@ public int getTotalLength() { public int getTotalLength(int start, int length) { int total = length; - for (SitemeshBufferFragment fragment : bufferFragments) { - if (fragment.getPosition() < start) { + for (Map.Entry entry : bufferFragments.entrySet()) { + int fragmentPosition = entry.getKey(); + if (fragmentPosition < start) { continue; } - if (fragment.getPosition() > start + length) { + if (fragmentPosition > start + length) { break; } - total += fragment.getTotalLength(); + total += entry.getValue().getTotalLength(); } return total; } @@ -82,4 +81,47 @@ public char[] getCharArray() { public boolean hasFragments() { return !bufferFragments.isEmpty(); } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(SitemeshBuffer sitemeshBuffer) { + return new Builder((DefaultSitemeshBuffer) sitemeshBuffer); + } + + public static class Builder { + private char[] buffer; + private int length; + private final TreeMap fragments; + + private Builder() { + this.fragments = new TreeMap(); + } + + private Builder(DefaultSitemeshBuffer buffer) { + this.buffer = buffer.buffer; + this.length = buffer.length; + this.fragments = new TreeMap(buffer.bufferFragments); + } + + public Builder setBuffer(char[] buffer) { + this.buffer = buffer; + return this; + } + + public Builder setLength(int length) { + this.length = length; + return this; + } + + public Builder insert(int position, SitemeshBufferFragment fragment) { + this.fragments.put(position, fragment); + return this; + } + + public SitemeshBuffer build() { + return new DefaultSitemeshBuffer(buffer, length, fragments); + } + } } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java index feae7dbe..850dda3c 100644 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java @@ -1,66 +1,95 @@ package com.opensymphony.module.sitemesh; +import com.opensymphony.module.sitemesh.html.util.StringSitemeshBuffer; + import java.io.IOException; +import java.io.StringWriter; import java.io.Writer; -import java.util.Collections; -import java.util.List; +import java.util.*; /** - * A fragment of a sitemesh buffer + * A fragment of a sitemesh buffer. This includes a start and a length, and may contain a list of deleted sections of + * the buffer. */ -public class SitemeshBufferFragment implements Comparable { +public class SitemeshBufferFragment { private final SitemeshBuffer buffer; - private final int position; private final int start; private final int length; - private final List deletions; + private final TreeMap deletions; - public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position) { - this(buffer, start, length, position, Collections.emptyList()); + public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length) { + this(buffer, start, length, new TreeMap()); } - public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, int position, List deletions) { + /** + * Create a sitemesh buffer fragment + * + * @param buffer The buffer that this is a fragment of + * @param start The start of the fragment + * @param length The length of the fragment + * @param deletions Deleted parts of the fragment, as a map of positions to the length to be deleted. + */ + public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, TreeMap deletions) { this.buffer = buffer; this.start = start; this.length = length; - this.position = position; this.deletions = deletions; } - public void writeTo(Writer writer) throws IOException - { + /** + * Write the fragment to the given writer + * + * @param writer The writer to write the fragment to + * @throws IOException If an error occured + */ + public void writeTo(Writer writer) throws IOException { int pos = start; - for (SitemeshBufferFragmentDeletion deletion : deletions) { - if (deletion.getStart() > pos) { - buffer.writeTo(writer, pos, deletion.getStart() - pos); + for (Map.Entry delete : deletions.entrySet()) { + int deletePos = delete.getKey(); + if (deletePos >= pos) { + buffer.writeTo(writer, pos, deletePos - pos); } - pos = deletion.getStart() + deletion.getLength(); + pos = Math.max(deletePos + delete.getValue(), start); } int remain = start + length - pos; - if (remain > 0) - { + if (remain >= 0) { buffer.writeTo(writer, pos, remain); } } - public int getTotalLength() - { + /** + * Get the total length of the fragment, taking deletions and chained buffers of the buffer + * + * @return The total length of the fragment + */ + public int getTotalLength() { int total = 0; int pos = start; - for (SitemeshBufferFragmentDeletion deletion : deletions) { - if (deletion.getStart() > pos) { - total += buffer.getTotalLength(pos, deletion.getStart() - pos); + for (Map.Entry delete : deletions.entrySet()) { + int deletePos = delete.getKey(); + if (deletePos > pos) { + total += buffer.getTotalLength(pos, deletePos - pos); } - pos = deletion.getStart() + deletion.getLength(); + pos = deletePos + delete.getValue(); } int remain = start + length - pos; - if (remain > 0) - { - total += remain; + if (remain > 0) { + total += buffer.getTotalLength(pos, remain); } return total; } + @Override + public String toString() { + StringWriter writer = new StringWriter(); + try { + writeTo(writer); + } catch (IOException e) { + throw new RuntimeException("Exception writing to buffer", e); + } + return writer.toString(); + } + public int getStart() { return start; } @@ -69,11 +98,156 @@ public int getLength() { return length; } - public int getPosition() { - return position; + public static Builder builder() { + return new Builder(); } - public int compareTo(SitemeshBufferFragment o) { - return o.position - position; + public static Builder builder(SitemeshBufferFragment fragment) { + return new Builder(fragment); + } + + /** + * A builder for fragments. + */ + public static class Builder { + private DefaultSitemeshBuffer.Builder buffer; + private int start; + private int length; + private final TreeMap deletions; + + private Integer startDelete; + + private Builder() { + this.deletions = new TreeMap(); + } + + private Builder(SitemeshBufferFragment fragment) { + this.buffer = DefaultSitemeshBuffer.builder(fragment.buffer); + this.start = fragment.start; + this.length = fragment.length; + this.deletions = new TreeMap(fragment.deletions); + } + + public Builder setStart(int start) { + this.start = start; + return this; + } + + public Builder setLength(int length) { + this.length = length; + return this; + } + + /** + * Delete length characters from pos in this buffer fragment + * + * @param pos The position to delete from + * @param length The number of characters to delete + * @return The builder + */ + public Builder delete(int pos, int length) { + this.deletions.put(pos, length); + return this; + } + + /** + * Mark the start of the fragment + * + * @param pos The start of the fragment + * @return The builder + */ + public Builder markStart(int pos) { + this.start = pos; + this.length = 0; + return this; + } + + /** + * End the fragment + * + * @param pos The position of the end of the fragment + * @return The builder + */ + public Builder end(int pos) { + this.length = pos - this.start; + return this; + } + + /** + * Mark the start of a deletion. + * + * @param pos The position to start deleting from + * @return The builder + * @throws IllegalStateException If markStartDelete() has already been called and endDelete() hasn't been called + */ + public Builder markStartDelete(int pos) { + if (startDelete != null) { + throw new IllegalStateException("Can't nested delete..."); + } + startDelete = pos; + return this; + } + + /** + * End the current deletion + * + * @param pos The position to delete to + * @return The builder + * @throws IllegalStateException If markStartDelete() hasn't been called + */ + public Builder endDelete(int pos) { + if (startDelete == null) { + throw new IllegalStateException("Ending delete with no start delete..."); + } + delete(startDelete, pos - startDelete); + startDelete = null; + return this; + } + + /** + * Insert the given fragment to the given position + * + * @param position The position to insert the fragment to + * @param fragment The fragment to insert + * @return The builder + */ + public Builder insert(int position, SitemeshBufferFragment fragment) { + buffer.insert(position, fragment); + return this; + } + + /** + * Insert the given string fragment to the given position + * + * @param position The position to insert at + * @param fragment The fragment to insert + * @return The builder + */ + public Builder insert(int position, String fragment) { + buffer.insert(position, StringSitemeshBuffer.createBufferFragment(fragment)); + return this; + } + + /** + * Set the buffer. This resets both start and length to be that of the buffer. + * + * @param sitemeshBuffer The buffer to set. + * @return The builder + */ + public Builder setBuffer(SitemeshBuffer sitemeshBuffer) { + this.buffer = DefaultSitemeshBuffer.builder(sitemeshBuffer); + this.start = 0; + this.length = sitemeshBuffer.getBufferLength(); + return this; + } + + /** + * Build the fragment + * + * @return The built fragment + */ + public SitemeshBufferFragment build() { + return new SitemeshBufferFragment(buffer.build(), start, length, deletions); + } } } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java deleted file mode 100644 index 6eb1fb05..00000000 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragmentDeletion.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.opensymphony.module.sitemesh; - -/** - * - */ -public class SitemeshBufferFragmentDeletion -{ - private final int start; - private final int length; - - public SitemeshBufferFragmentDeletion(int start, int length) - { - this.start = start; - this.length = length; - } - - public int getStart() - { - return start; - } - - public int getLength() - { - return length; - } -} diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java index 75108e95..18c35fac 100644 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferWriter.java @@ -3,8 +3,7 @@ import com.opensymphony.module.sitemesh.util.CharArrayWriter; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.TreeMap; /** * A char array writer that caches other sitemesh buffers written to it, so that it doesn't have to continually copy @@ -12,7 +11,7 @@ */ public class SitemeshBufferWriter extends CharArrayWriter implements SitemeshWriter { - private final List fragments = new ArrayList(); + private final TreeMap fragments = new TreeMap(); public SitemeshBufferWriter() { } @@ -21,8 +20,8 @@ public SitemeshBufferWriter(int initialSize) { super(initialSize); } - public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException { - fragments.add(new SitemeshBufferFragment(sitemeshBuffer, start, length, count)); + public boolean writeSitemeshBufferFragment(SitemeshBufferFragment bufferFragment) throws IOException { + fragments.put(count, bufferFragment); return false; } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java b/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java index 29264a49..e17d923d 100644 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshWriter.java @@ -8,16 +8,14 @@ */ public interface SitemeshWriter { /** - * Write a sitemesh buffer to the writer. This may not be written immediately, it may be stored and written later, - * when this buffer is written out to a writer. + * Write a sitemesh buffer fragment to the writer. This may not be written immediately, it may be stored and + * written later, when this buffer is written out to a writer. * - * @param sitemeshBuffer The buffer to write - * @param start The place in the buffer to write from - * @param length The length of the buffer to write + * @param bufferFragment The buffer fragment to write * @return True if the buffer was written immediately, or false if it will be written later * @throws IOException If an IOException occurred */ - boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException; + boolean writeSitemeshBufferFragment(SitemeshBufferFragment bufferFragment) throws IOException; /** * Get the underlying buffer for the writer diff --git a/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java b/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java deleted file mode 100644 index 040ebaf8..00000000 --- a/src/java/com/opensymphony/module/sitemesh/filter/BufferedContent.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.opensymphony.module.sitemesh.filter; - -import java.util.TreeMap; - -public class BufferedContent { - private final char[] buffer; - private final int length; - - public BufferedContent(char[] buffer) { - this.buffer = buffer; - this.length = buffer.length; - } - - public BufferedContent(char[] buffer, int length) { - this.buffer = buffer; - this.length = length; - } - - public char[] getBuffer() { - return buffer; - } - - public int getLength() { - return length; - } -} diff --git a/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java b/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java index acc9832c..4f022c12 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/RoutablePrintWriter.java @@ -9,6 +9,7 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.SitemeshWriter; /** @@ -183,13 +184,13 @@ public void close() throws IOException { } - public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException + public boolean writeSitemeshBufferFragment(SitemeshBufferFragment bufferFragment) throws IOException { PrintWriter destination = getDestination(); if (destination instanceof SitemeshWriter) { - return ((SitemeshWriter) destination).writeSitemeshBuffer(sitemeshBuffer, start, length); + return ((SitemeshWriter) destination).writeSitemeshBufferFragment(bufferFragment); } else { - sitemeshBuffer.writeTo(destination, start, length); + bufferFragment.writeTo(destination); return true; } } diff --git a/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java b/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java index e8b8e3ce..7af404d5 100644 --- a/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java +++ b/src/java/com/opensymphony/module/sitemesh/filter/SitemeshPrintWriter.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.filter; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.SitemeshBufferWriter; import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.SitemeshWriter; @@ -19,8 +20,8 @@ public SitemeshPrintWriter(SitemeshBufferWriter sitemeshWriter) { this.sitemeshWriter = sitemeshWriter; } - public boolean writeSitemeshBuffer(SitemeshBuffer sitemeshBuffer, int start, int length) throws IOException { - return sitemeshWriter.writeSitemeshBuffer(sitemeshBuffer, start, length); + public boolean writeSitemeshBufferFragment(SitemeshBufferFragment bufferFragment) throws IOException { + return sitemeshWriter.writeSitemeshBufferFragment(bufferFragment); } public SitemeshBuffer getSitemeshBuffer() { diff --git a/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java b/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java index 9501a06b..0f7ea707 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java @@ -1,7 +1,11 @@ package com.opensymphony.module.sitemesh.html; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.util.CharArray; +import java.io.IOException; +import java.io.StringWriter; + public abstract class BasicRule implements TagRule { private final String[] acceptableTagNames; @@ -38,8 +42,12 @@ public boolean shouldProcess(String name) { public abstract void process(Tag tag); - protected CharArray currentBuffer() { + protected SitemeshBufferFragment.Builder currentBuffer() { return context.currentBuffer(); } + protected String getCurrentBufferContent() { + return context.currentBuffer().build().toString(); + } + } diff --git a/src/java/com/opensymphony/module/sitemesh/html/BlockExtractingRule.java b/src/java/com/opensymphony/module/sitemesh/html/BlockExtractingRule.java index b9256ae0..08dd94e8 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/BlockExtractingRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/BlockExtractingRule.java @@ -1,38 +1,42 @@ package com.opensymphony.module.sitemesh.html; -import com.opensymphony.module.sitemesh.html.util.CharArray; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; public abstract class BlockExtractingRule extends BasicRule { - private boolean includeEnclosingTags; + private boolean keepInBuffer; // we should only handle tags that have been opened previously. // else the parser throws a NoSuchElementException (SIM-216) private boolean seenOpeningTag; - protected BlockExtractingRule(boolean includeEnclosingTags, String acceptableTagName) { + protected BlockExtractingRule(boolean keepInBuffer, String acceptableTagName) { super(acceptableTagName); - this.includeEnclosingTags = includeEnclosingTags; + this.keepInBuffer = keepInBuffer; } - protected BlockExtractingRule(boolean includeEnclosingTags) { - this.includeEnclosingTags = includeEnclosingTags; + protected BlockExtractingRule(boolean keepInBuffer) { + this.keepInBuffer = keepInBuffer; } public void process(Tag tag) { if (tag.getType() == Tag.OPEN) { - if (includeEnclosingTags) { - tag.writeTo(context.currentBuffer()); + if (!keepInBuffer) { + context.currentBuffer().markStartDelete(tag.getPosition()); } - context.pushBuffer(createBuffer()); + context.pushBuffer(createBuffer(context.getSitemeshBuffer()).markStart(tag.getPosition() + tag.getLength())); start(tag); seenOpeningTag = true; } else if (tag.getType() == Tag.CLOSE && seenOpeningTag) { + context.currentBuffer().end(tag.getPosition()); end(tag); context.popBuffer(); - if (includeEnclosingTags) { - tag.writeTo(context.currentBuffer()); + if (!keepInBuffer) { + context.currentBuffer().endDelete(tag.getPosition() + tag.getLength()); } + } else if (!keepInBuffer) { + context.currentBuffer().delete(tag.getPosition(), tag.getLength()); } } @@ -42,8 +46,8 @@ protected void start(Tag tag) { protected void end(Tag tag) { } - protected CharArray createBuffer() { - return new CharArray(512); + protected SitemeshBufferFragment.Builder createBuffer(SitemeshBuffer sitemeshBuffer) { + return SitemeshBufferFragment.builder().setBuffer(sitemeshBuffer); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java b/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java index bb5c982e..78df175c 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java +++ b/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java @@ -1,9 +1,14 @@ package com.opensymphony.module.sitemesh.html; +import java.io.IOException; +import java.io.StringWriter; import java.util.Arrays; +import java.util.TreeMap; -import com.opensymphony.module.sitemesh.html.util.CharArray; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.tokenizer.Parser; +import com.opensymphony.module.sitemesh.html.util.StringSitemeshBuffer; /** @@ -60,12 +65,13 @@ public CustomTag(Tag tag) { } public String getContents() { - CharArray c = new CharArray(64); - writeTo(c); - return c.toString(); + SitemeshBufferFragment.Builder buffer = SitemeshBufferFragment.builder().setBuffer(new DefaultSitemeshBuffer(new char[]{})); + writeTo(buffer, 0); + return buffer.build().toString(); } - public void writeTo(CharArray out) { + public void writeTo(SitemeshBufferFragment.Builder buffer, int position) { + StringWriter out = new StringWriter(); if (type == Tag.CLOSE) { out.append("'); } + buffer.insert(position, StringSitemeshBuffer.createBufferFragment(out.toString())); } public boolean equals(Object o) { @@ -284,4 +291,12 @@ public void removeAttribute(String name, boolean caseSensitive) { removeAttribute(attributeIndex); } } + + public int getPosition() { + return 0; + } + + public int getLength() { + return 0; + } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessor.java b/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessor.java index 12e46c0c..9a37dd14 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessor.java +++ b/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessor.java @@ -1,39 +1,23 @@ package com.opensymphony.module.sitemesh.html; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.tokenizer.TagTokenizer; import com.opensymphony.module.sitemesh.html.tokenizer.TokenHandler; -import com.opensymphony.module.sitemesh.html.util.CharArray; -import com.opensymphony.module.sitemesh.util.CharArrayWriter; -import java.io.Reader; -import java.io.Writer; -//import java.io.CharArrayWriter; import java.io.IOException; public class HTMLProcessor { - private final char[] in; - private final CharArray out; + private final SitemeshBuffer sitemeshBuffer; + private final SitemeshBufferFragment.Builder body; private final State defaultState = new State(); private State currentState = defaultState; - private Writer outStream; - public HTMLProcessor(char[] in, CharArray out) { - this.in = in; - this.out = out; - } - - public HTMLProcessor(Reader in, Writer out) throws IOException { - CharArrayWriter inBuffer = new CharArrayWriter(); - char[] buffer = new char[2048]; - int n; - while (-1 != (n = in.read(buffer))) { - inBuffer.write(buffer, 0, n); - } - this.in = inBuffer.toCharArray(); - this.out = new CharArray(2048); - this.outStream = out; + public HTMLProcessor(SitemeshBuffer sitemeshBuffer, SitemeshBufferFragment.Builder body) { + this.sitemeshBuffer = sitemeshBuffer; + this.body = body; } public State defaultState() { @@ -48,8 +32,13 @@ public void addRule(TagRule rule) { } public void process() throws IOException { - TagTokenizer tokenizer = new TagTokenizer(in); + TagTokenizer tokenizer = new TagTokenizer(sitemeshBuffer.getCharArray(), sitemeshBuffer.getBufferLength()); final HTMLProcessorContext context = new HTMLProcessorContext() { + + public SitemeshBuffer getSitemeshBuffer() { + return sitemeshBuffer; + } + public State currentState() { return currentState; } @@ -58,35 +47,29 @@ public void changeState(State newState) { currentState = newState; } - private CharArray[] buffers = new CharArray[10]; + private SitemeshBufferFragment.Builder[] buffers = new SitemeshBufferFragment.Builder[10]; private int size; - public void pushBuffer(CharArray buffer) { + public void pushBuffer(SitemeshBufferFragment.Builder buffer) { if(size == buffers.length) { - CharArray[] newBuffers = new CharArray[buffers.length * 2]; + SitemeshBufferFragment.Builder[] newBuffers = new SitemeshBufferFragment.Builder[buffers.length * 2]; System.arraycopy(buffers, 0, newBuffers, 0, buffers.length); buffers = newBuffers; } buffers[size++] = buffer; } - public CharArray currentBuffer() { + public SitemeshBufferFragment.Builder currentBuffer() { return buffers[size - 1]; } - public CharArray popBuffer() { - CharArray last = buffers[size - 1]; + public SitemeshBufferFragment.Builder popBuffer() { + SitemeshBufferFragment.Builder last = buffers[size - 1]; buffers[--size] = null; return last; } - - public void mergeBuffer() { - CharArray top = buffers[size - 1]; - CharArray nextDown = buffers[size - 2]; - nextDown.append(top); - } }; - context.pushBuffer(out); + context.pushBuffer(body); tokenizer.start(new TokenHandler() { public boolean shouldProcessTag(String name) { @@ -109,9 +92,6 @@ public void warning(String message, int line, int column) { } }); defaultState.endOfState(); - if (outStream != null) { - outStream.write(out.toString()); - } } public void addTextFilter(TextFilter textFilter) { diff --git a/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessorContext.java b/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessorContext.java index 28140bc4..e581b012 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessorContext.java +++ b/src/java/com/opensymphony/module/sitemesh/html/HTMLProcessorContext.java @@ -1,14 +1,17 @@ package com.opensymphony.module.sitemesh.html; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.util.CharArray; public interface HTMLProcessorContext { + SitemeshBuffer getSitemeshBuffer(); + State currentState(); void changeState(State newState); - void pushBuffer(CharArray buffer); - CharArray currentBuffer(); - CharArray popBuffer(); - void mergeBuffer(); + void pushBuffer(SitemeshBufferFragment.Builder fragment); + SitemeshBufferFragment.Builder currentBuffer(); + SitemeshBufferFragment.Builder popBuffer(); } diff --git a/src/java/com/opensymphony/module/sitemesh/html/State.java b/src/java/com/opensymphony/module/sitemesh/html/State.java index 52aaa353..3af4edb6 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/State.java +++ b/src/java/com/opensymphony/module/sitemesh/html/State.java @@ -1,5 +1,8 @@ package com.opensymphony.module.sitemesh.html; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.html.util.StringSitemeshBuffer; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -60,15 +63,17 @@ public void endOfState() { } public void handleText(Text text, HTMLProcessorContext context) { - if (textFilters == null) { - text.writeTo(context.currentBuffer()); - } else { - String asString = text.getContents(); + if (textFilters != null && !textFilters.isEmpty()) { + String original = text.getContents(); + String asString = original; for (Iterator iterator = textFilters.iterator(); iterator.hasNext();) { TextFilter textFilter = (TextFilter) iterator.next(); asString = textFilter.filter(asString); } - context.currentBuffer().append(asString); + if (!original.equals(asString)) { + context.currentBuffer().delete(text.getPosition(), text.getLength()); + context.currentBuffer().insert(text.getPosition(), StringSitemeshBuffer.createBufferFragment(asString)); + } } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/StateTransitionRule.java b/src/java/com/opensymphony/module/sitemesh/html/StateTransitionRule.java index 167fe5c0..fd3b348e 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/StateTransitionRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/StateTransitionRule.java @@ -26,8 +26,5 @@ public void process(Tag tag) { context.changeState(lastState); lastState = null; } - if (writeEnclosingTag) { - tag.writeTo(context.currentBuffer()); - } } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/Tag.java b/src/java/com/opensymphony/module/sitemesh/html/Tag.java index f3bc362b..c42c9481 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/Tag.java +++ b/src/java/com/opensymphony/module/sitemesh/html/Tag.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.html; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.util.CharArray; /** @@ -25,15 +26,13 @@ public interface Tag { * Get the complete tag in its original form, preserving original formatting. * * This has a slight overhead in that it needs to construct a String. For improved performance, use writeTo() instead. - * - * @see #writeTo(com.opensymphony.module.sitemesh.html.util.CharArray) */ String getContents(); /** * Write out the complete tag in its original form, preserving original formatting. */ - void writeTo(CharArray out); + void writeTo(SitemeshBufferFragment.Builder fragment, int position); /** * Name of tag (ie. element name). @@ -78,4 +77,14 @@ public interface Tag { */ boolean hasAttribute(String name, boolean caseSensitive); + /** + * The position of the tag + */ + int getPosition(); + + /** + * The length of the tag + */ + int getLength(); + } diff --git a/src/java/com/opensymphony/module/sitemesh/html/Text.java b/src/java/com/opensymphony/module/sitemesh/html/Text.java index 8aac83b7..2905914f 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/Text.java +++ b/src/java/com/opensymphony/module/sitemesh/html/Text.java @@ -1,6 +1,6 @@ package com.opensymphony.module.sitemesh.html; -import com.opensymphony.module.sitemesh.html.util.CharArray; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; /** * Text returned by HTMLTagTokenizer. @@ -16,14 +16,22 @@ public interface Text { * Get the complete contents of the text block, preserving original formatting. * * This has a slight overhead in that it needs to construct a String. For improved performance, use writeTo() instead. - * - * @see #writeTo(com.opensymphony.module.sitemesh.html.util.CharArray) */ String getContents(); /** * Write out the complete contents of the text block, preserving original formatting. */ - void writeTo(CharArray out); + void writeTo(SitemeshBufferFragment.Builder buffer, int position); + + /** + * The position of the text + */ + int getPosition(); + + /** + * The length of the text + */ + int getLength(); } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/BodyTagRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/BodyTagRule.java index 8bcd5408..4edc45f6 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/BodyTagRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/BodyTagRule.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.html.rules; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.BasicRule; import com.opensymphony.module.sitemesh.html.Tag; import com.opensymphony.module.sitemesh.html.util.CharArray; @@ -7,9 +8,9 @@ public class BodyTagRule extends BasicRule { private final PageBuilder page; - private final CharArray body; + private final SitemeshBufferFragment.Builder body; - public BodyTagRule(PageBuilder page, CharArray body) { + public BodyTagRule(PageBuilder page, SitemeshBufferFragment.Builder body) { super("body"); this.page = page; this.body = body; @@ -17,12 +18,14 @@ public BodyTagRule(PageBuilder page, CharArray body) { public void process(Tag tag) { if (tag.getType() == Tag.OPEN || tag.getType() == Tag.EMPTY) { + context.currentBuffer().setStart(tag.getPosition() + tag.getLength()); for (int i = 0; i < tag.getAttributeCount(); i++) { page.addProperty("body." + tag.getAttributeName(i), tag.getAttributeValue(i)); } - body.clear(); + body.markStart(tag.getPosition() + tag.getLength()); } else { - context.pushBuffer(new CharArray(64)); // unused buffer: everything after is discarded. + body.end(tag.getPosition()); + context.pushBuffer(SitemeshBufferFragment.builder()); // unused buffer: everything after is discarded. } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/ContentBlockExtractingRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/ContentBlockExtractingRule.java index cafea02e..9a50d7fd 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/ContentBlockExtractingRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/ContentBlockExtractingRule.java @@ -19,7 +19,7 @@ protected void start(Tag tag) { } protected void end(Tag tag) { - page.addProperty("page." + contentBlockId, currentBuffer().toString()); + page.addProperty("page." + contentBlockId, getCurrentBufferContent()); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/FramesetRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/FramesetRule.java index 04da5224..fe098c2d 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/FramesetRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/FramesetRule.java @@ -13,6 +13,7 @@ public FramesetRule(PageBuilder page) { } public void process(Tag tag) { + context.currentBuffer().delete(tag.getPosition(), tag.getLength()); page.addProperty("frameset", "true"); } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/HeadExtractingRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/HeadExtractingRule.java index b2d2a2ac..a5e10b3a 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/HeadExtractingRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/HeadExtractingRule.java @@ -1,18 +1,20 @@ package com.opensymphony.module.sitemesh.html.rules; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.BlockExtractingRule; import com.opensymphony.module.sitemesh.html.util.CharArray; public class HeadExtractingRule extends BlockExtractingRule { - private final CharArray head; + private final SitemeshBufferFragment.Builder head; - public HeadExtractingRule(CharArray head) { + public HeadExtractingRule(SitemeshBufferFragment.Builder head) { super(false, "head"); this.head = head; } - protected CharArray createBuffer() { + protected SitemeshBufferFragment.Builder createBuffer(SitemeshBuffer sitemeshBuffer) { return head; } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/HtmlAttributesRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/HtmlAttributesRule.java index 26aeb976..c4d68331 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/HtmlAttributesRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/HtmlAttributesRule.java @@ -14,9 +14,12 @@ public HtmlAttributesRule(PageBuilder page) { public void process(Tag tag) { if (tag.getType() == Tag.OPEN) { + context.currentBuffer().markStart(tag.getPosition() + tag.getLength()); for (int i = 0; i < tag.getAttributeCount(); i++) { page.addProperty(tag.getAttributeName(i), tag.getAttributeValue(i)); } + } else { + context.currentBuffer().end(tag.getPosition()); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/MSOfficeDocumentPropertiesRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/MSOfficeDocumentPropertiesRule.java index 49f775ff..3b24960f 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/MSOfficeDocumentPropertiesRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/MSOfficeDocumentPropertiesRule.java @@ -26,7 +26,6 @@ public boolean shouldProcess(String name) { public void process(Tag tag) { if (tag.getName().equals("o:DocumentProperties")) { inDocumentProperties = (tag.getType() == Tag.OPEN); - tag.writeTo(currentBuffer()); } else { super.process(tag); } @@ -37,8 +36,7 @@ protected void start(Tag tag) { protected void end(Tag tag) { String name = tag.getName().substring(2); - page.addProperty("office.DocumentProperties." + name, currentBuffer().toString()); - context.mergeBuffer(); + page.addProperty("office.DocumentProperties." + name, getCurrentBufferContent()); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/MetaTagRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/MetaTagRule.java index fcf5d1c1..6998d4f9 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/MetaTagRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/MetaTagRule.java @@ -18,6 +18,5 @@ public void process(Tag tag) { } else if (tag.hasAttribute("http-equiv", false)) { page.addProperty("meta.http-equiv." + tag.getAttributeValue("http-equiv", false), tag.getAttributeValue("content", false)); } - tag.writeTo(currentBuffer()); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/ParameterExtractingRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/ParameterExtractingRule.java index 76a95fd8..11e41536 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/ParameterExtractingRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/ParameterExtractingRule.java @@ -13,6 +13,7 @@ public ParameterExtractingRule(PageBuilder page) { } public void process(Tag tag) { + context.currentBuffer().delete(tag.getPosition(), tag.getLength()); page.addProperty("page." + tag.getAttributeValue("name", false), tag.getAttributeValue("value", false)); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/TagReplaceRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/TagReplaceRule.java index 1d046642..e0adda3a 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/TagReplaceRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/TagReplaceRule.java @@ -22,8 +22,9 @@ public TagReplaceRule(String originalTagName, String newTagName) { } public void process(Tag tag) { + currentBuffer().delete(tag.getPosition(), tag.getLength()); CustomTag customTag = new CustomTag(tag); customTag.setName(newTagName); - customTag.writeTo(currentBuffer()); + customTag.writeTo(currentBuffer(), tag.getPosition()); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/rules/TitleExtractingRule.java b/src/java/com/opensymphony/module/sitemesh/html/rules/TitleExtractingRule.java index 076d731f..672ae399 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/rules/TitleExtractingRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/rules/TitleExtractingRule.java @@ -16,7 +16,7 @@ public TitleExtractingRule(PageBuilder page) { protected void end(Tag tag) { if (!seenTitle) { - page.addProperty("title", currentBuffer().toString()); + page.addProperty("title", getCurrentBufferContent()); seenTitle = true; } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/tokenizer/Parser.java b/src/java/com/opensymphony/module/sitemesh/html/tokenizer/Parser.java index b2200108..e2bbd8ec 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/tokenizer/Parser.java +++ b/src/java/com/opensymphony/module/sitemesh/html/tokenizer/Parser.java @@ -6,6 +6,8 @@ package com.opensymphony.module.sitemesh.html.tokenizer; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.Tag; import com.opensymphony.module.sitemesh.html.Text; import com.opensymphony.module.sitemesh.html.util.CharArray; @@ -49,8 +51,8 @@ public class Parser extends Lexer { private String name; private int type; - public Parser(char[] input, TokenHandler handler) { - super(new CharArrayReader(input)); + public Parser(char[] input, int length, TokenHandler handler) { + super(new CharArrayReader(input, 0, length)); this.input = input; this.handler = handler; } @@ -339,8 +341,8 @@ public String getContents() { return new String(input, position, length); } - public void writeTo(CharArray out) { - out.append(input, position, length); + public void writeTo(SitemeshBufferFragment.Builder buffer, int position) { + buffer.insert(position, SitemeshBufferFragment.builder().setBuffer(new DefaultSitemeshBuffer(input)).setStart(position).setLength(length).build()); } public int getAttributeCount() { @@ -385,5 +387,13 @@ public boolean hasAttribute(String name, boolean caseSensitive) { return getAttributeIndex(name, caseSensitive) > -1; } + public int getPosition() { + return position; + } + + public int getLength() { + return length; + } + } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/tokenizer/TagTokenizer.java b/src/java/com/opensymphony/module/sitemesh/html/tokenizer/TagTokenizer.java index 37425e6e..aa1945bc 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/tokenizer/TagTokenizer.java +++ b/src/java/com/opensymphony/module/sitemesh/html/tokenizer/TagTokenizer.java @@ -19,9 +19,15 @@ public class TagTokenizer { private final char[] input; + private final int length; public TagTokenizer(char[] input) { + this(input, input.length); + } + + public TagTokenizer(char[] input, int length) { this.input = input; + this.length = length; } public TagTokenizer(String input) { @@ -29,7 +35,7 @@ public TagTokenizer(String input) { } public void start(TokenHandler handler) { - Parser parser = new Parser(input, handler); + Parser parser = new Parser(input, length, handler); parser.start(); } diff --git a/src/java/com/opensymphony/module/sitemesh/html/util/StringSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/html/util/StringSitemeshBuffer.java new file mode 100644 index 00000000..1b5f72b4 --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/html/util/StringSitemeshBuffer.java @@ -0,0 +1,48 @@ +package com.opensymphony.module.sitemesh.html.util; + +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; + +import java.io.IOException; +import java.io.Writer; + +/** + * SitemeshBuffer that is a string + */ +public class StringSitemeshBuffer implements SitemeshBuffer { + private final String buffer; + + public StringSitemeshBuffer(String buffer) { + this.buffer = buffer; + } + + public char[] getCharArray() { + return buffer.toCharArray(); + } + + public int getBufferLength() { + return buffer.length(); + } + + public int getTotalLength() { + return buffer.length(); + } + + public int getTotalLength(int start, int length) { + return length; + } + + public void writeTo(Writer writer, int start, int length) throws IOException { + writer.write(buffer, start, length); + } + + public boolean hasFragments() { + return false; + } + + public static SitemeshBufferFragment createBufferFragment(String buffer) + { + return new SitemeshBufferFragment(new StringSitemeshBuffer(buffer), 0, buffer.length()); + } + +} diff --git a/src/java/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParser.java b/src/java/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParser.java index 690e6600..8c2f630b 100644 --- a/src/java/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/multipass/DivExtractingPageParser.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.multipass; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.BasicRule; import com.opensymphony.module.sitemesh.html.State; import com.opensymphony.module.sitemesh.html.Tag; @@ -7,6 +8,8 @@ import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.parser.HTMLPageParser; +import java.nio.channels.GatheringByteChannel; + public class DivExtractingPageParser extends HTMLPageParser { protected void addUserDefinedRules(State html, final PageBuilder page) { @@ -28,19 +31,21 @@ public void process(Tag tag) { if (tag.getType() == Tag.OPEN) { String id = tag.getAttributeValue("id", false); if (depth == 0 && id != null) { - currentBuffer().append(""); + currentBuffer().insert(tag.getPosition(), ""); blockId = id; - context.pushBuffer(new CharArray(512)); + currentBuffer().markStartDelete(tag.getPosition()); + context.pushBuffer(SitemeshBufferFragment.builder().setBuffer(context.getSitemeshBuffer())); + currentBuffer().markStart(tag.getPosition()); } - tag.writeTo(currentBuffer()); depth++; } else if (tag.getType() == Tag.CLOSE) { depth--; - tag.writeTo(currentBuffer()); if (depth == 0 && blockId != null) { - page.addProperty("div." + blockId, currentBuffer().toString()); + currentBuffer().end(tag.getPosition() + tag.getLength()); + page.addProperty("div." + blockId, getCurrentBufferContent()); blockId = null; context.popBuffer(); + currentBuffer().endDelete(tag.getPosition() + tag.getLength()); } } } diff --git a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java index 6d10bef9..dab2ebd9 100644 --- a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java @@ -3,6 +3,7 @@ import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.html.HTMLProcessor; import com.opensymphony.module.sitemesh.html.BasicRule; @@ -20,47 +21,21 @@ public MultipassReplacementPageParser(Page page, HttpServletResponse response) { this.response = response; } - public Page parse(SitemeshBuffer buffer) throws IOException { - char[] data; - int length; - if (buffer.hasFragments()) { - // Write the buffer into a char array - com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength()); - buffer.writeTo(writer, 0, buffer.getBufferLength()); - data = writer.toCharArray(); - length = data.length; - } else { - data = buffer.getCharArray(); - length = buffer.getBufferLength(); - } - return parse(data, length); - } - - private Page parse(char[] data, int length) throws IOException - { - if (data.length > length) { - // todo fix this parser so that it doesn't need to compact the array - char[] newData = new char[length]; - System.arraycopy(data, 0, newData, 0, length); - data = newData; - } - return parse(data); - } - - private Page parse(char[] data) throws IOException { - final CharArray result = new CharArray(4096); - HTMLProcessor processor = new HTMLProcessor(data, result); + public Page parse(SitemeshBuffer sitemeshBuffer) throws IOException { + SitemeshBufferFragment.Builder builder = SitemeshBufferFragment.builder().setBuffer(sitemeshBuffer); + HTMLProcessor processor = new HTMLProcessor(sitemeshBuffer, builder); processor.addRule(new BasicRule("sitemesh:multipass") { public void process(Tag tag) { + currentBuffer().delete(tag.getPosition(), tag.getLength()); String id = tag.getAttributeValue("id", true); if (!page.isPropertySet("_sitemesh.removefrompage." + id)) { - currentBuffer().append(page.getProperty(id)); + currentBuffer().insert(tag.getPosition(), page.getProperty(id)); } } }); processor.process(); - result.writeTo(response.getWriter()); + builder.build().writeTo(response.getWriter()); return null; } } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java b/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java index 6b5ee0c8..b6daaeb6 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/AbstractHTMLPage.java @@ -10,6 +10,7 @@ package com.opensymphony.module.sitemesh.parser; import com.opensymphony.module.sitemesh.HTMLPage; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import java.io.IOException; import java.io.Writer; @@ -29,6 +30,10 @@ */ public abstract class AbstractHTMLPage extends AbstractPage implements HTMLPage { + protected AbstractHTMLPage(SitemeshBuffer sitemeshBuffer) { + super(sitemeshBuffer); + } + /** * Write data of html <head> tag. * diff --git a/src/java/com/opensymphony/module/sitemesh/parser/AbstractPage.java b/src/java/com/opensymphony/module/sitemesh/parser/AbstractPage.java index 59977822..91cfe993 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/AbstractPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/AbstractPage.java @@ -10,6 +10,7 @@ package com.opensymphony.module.sitemesh.parser; import com.opensymphony.module.sitemesh.Page; +import com.opensymphony.module.sitemesh.SitemeshBuffer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @@ -22,13 +23,7 @@ * Abstract implementation of {@link com.opensymphony.module.sitemesh.Page} . * *

Contains base methods for storing and accessing page properties. - * Also stores {@link #pageData} as byte[] and implements write???() - * methods.

- * - *

Concrete implementations need only set the {@link #pageData} and - * call {@link #addProperty(java.lang.String,java.lang.String)} to - * add all the required information.

- * + * * * @author Joe Walnes * @version $Revision: 1.6 $ * @@ -42,13 +37,17 @@ public abstract class AbstractPage implements Page { private final Map properties = new HashMap(); /** Date of page contents. */ - protected char[] pageData = new char[0]; + private final SitemeshBuffer sitemeshBuffer; /** RequestURI of original Page. */ private HttpServletRequest request; + protected AbstractPage(SitemeshBuffer sitemeshBuffer) { + this.sitemeshBuffer = sitemeshBuffer; + } + public void writePage(Writer out) throws IOException { - out.write(pageData); + sitemeshBuffer.writeTo(out, 0, sitemeshBuffer.getBufferLength()); } public String getPage() { @@ -85,15 +84,18 @@ public String getTitle() { } public int getContentLength() { + // We encode it but not into a new buffer + CountingOutputStream counter = new CountingOutputStream(); try { - //todo - this needs to be fixed properly (SIM-196) - return new String(pageData).getBytes("UTF-8").length; // we cannot just measure pageData.length, due to i18n issues (SIM-157) - } - catch (UnsupportedEncodingException e) - { - return new String(pageData).getBytes().length; + OutputStreamWriter writer = new OutputStreamWriter(counter); + writePage(writer); + // We must flush, because the writer will buffer + writer.flush(); + } catch (IOException ioe) { + // Ignore, it's not possible with our OutputStream } + return counter.getCount(); } public String getProperty(String name) { @@ -177,6 +179,19 @@ public void addProperty(String name, String value) { protected static String noNull(String in) { return in == null ? "" : in; } + + private static class CountingOutputStream extends OutputStream { + private int count = 0; + + @Override + public void write(int i) throws IOException { + count++; + } + + public int getCount() { + return count; + } + } } class PageRequest extends HttpServletRequestWrapper { diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java index 1759663e..d279d38b 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPage.java @@ -9,6 +9,8 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.SitemeshBuffer; + import java.io.IOException; import java.io.Writer; import java.util.Iterator; @@ -25,9 +27,10 @@ public final class FastPage extends AbstractHTMLPage private String head; private String body; - public FastPage(Map sitemeshProps, Map htmlProps, Map metaProps, Map bodyProps, + public FastPage(SitemeshBuffer sitemeshBuffer, Map sitemeshProps, Map htmlProps, Map metaProps, Map bodyProps, String title, String head, String body, boolean frameSet) { + super(sitemeshBuffer); this.head = head; this.body = body; setFrameSet(frameSet); @@ -68,17 +71,6 @@ private void addAttributeList(String prefix, Map attributes) } } - public void setVerbatimPage(char[] v, int length) - { - if (v.length > length) { - // todo fix this parser so that it doesn't need to compact the array - char[] newData = new char[length]; - System.arraycopy(v, 0, newData, 0, length); - v = newData; - } - this.pageData = v; - } - public String getBody() { return body; diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java index 6426e19e..ac9a0ea6 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java @@ -87,31 +87,7 @@ public final class FastPageParser implements PageParser public Page parse(SitemeshBuffer buffer) throws IOException { - char[] data; - int length; - if (buffer.hasFragments()) { - // Write the buffer into a char array - CharArrayWriter writer = new CharArrayWriter(buffer.getTotalLength()); - buffer.writeTo(writer, 0, buffer.getBufferLength()); - data = writer.toCharArray(); - length = data.length; - } else { - data = buffer.getCharArray(); - length = buffer.getBufferLength(); - } - - FastPage page = internalParse(new CharArrayReader(data, 0, length)); - page.setVerbatimPage(data, length); - return page; - } - - public Page parse(Reader reader) - { - return internalParse(reader); - } - - private FastPage internalParse(Reader reader) - { + CharArrayReader reader = new CharArrayReader(buffer.getCharArray(), 0, buffer.getBufferLength()); CharArray _buffer = new CharArray(4096); CharArray _body = new CharArray(4096); CharArray _head = new CharArray(512); @@ -661,7 +637,7 @@ else if(c == '>' && _comment >= 7) _currentTaggedContent = null; _buffer = null; - return new FastPage(_sitemeshProperties, + return new FastPage(buffer, _sitemeshProperties, _htmlProperties, _metaProperties, _bodyProperties, diff --git a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java index 9c543ad5..1f000537 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java @@ -3,6 +3,7 @@ import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.HTMLProcessor; import com.opensymphony.module.sitemesh.html.State; import com.opensymphony.module.sitemesh.html.StateTransitionRule; @@ -34,37 +35,10 @@ public class HTMLPageParser implements PageParser { public Page parse(SitemeshBuffer buffer) throws IOException { - char[] data; - int length; - if (buffer.hasFragments()) { - // Write the buffer into a char array - com.opensymphony.module.sitemesh.util.CharArrayWriter writer = new com.opensymphony.module.sitemesh.util.CharArrayWriter(buffer.getTotalLength()); - buffer.writeTo(writer, 0, buffer.getBufferLength()); - data = writer.toCharArray(); - length = data.length; - } else { - data = buffer.getCharArray(); - length = buffer.getBufferLength(); - } - return parse(data, length); - } - - private Page parse(char[] data, int length) throws IOException - { - if (data.length > length) { - // todo fix this parser so that it doesn't need to compact the array - char[] newData = new char[length]; - System.arraycopy(data, 0, newData, 0, length); - data = newData; - } - return parse(data); - } - - private Page parse(char[] data) throws IOException { - CharArray head = new CharArray(64); - CharArray body = new CharArray(4096); - TokenizedHTMLPage page = new TokenizedHTMLPage(data, body, head); - HTMLProcessor processor = new HTMLProcessor(data, body); + SitemeshBufferFragment.Builder head = SitemeshBufferFragment.builder().setBuffer(buffer).setLength(0); + SitemeshBufferFragment.Builder body = SitemeshBufferFragment.builder().setBuffer(buffer); + TokenizedHTMLPage page = new TokenizedHTMLPage(buffer); + HTMLProcessor processor = new HTMLProcessor(buffer, body); State html = processor.defaultState(); // Core rules for SiteMesh to be functional. @@ -77,6 +51,8 @@ private Page parse(char[] data) throws IOException { addUserDefinedRules(html, page); processor.process(); + page.setBody(body.build()); + page.setHead(head.build()); return page; } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java new file mode 100644 index 00000000..2b14456e --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java @@ -0,0 +1,381 @@ +package com.opensymphony.module.sitemesh.parser; + +import java.io.IOException; +import java.util.*; + +import com.opensymphony.module.sitemesh.*; + +/** + * Page parser that doesn't parse the full page, but rather just parses the head section of the page. + * + * @since v2.5 + */ +public class PartialPageParser implements PageParser +{ + public Page parse(SitemeshBuffer buffer) throws IOException + { + char[] data = buffer.getCharArray(); + int length = buffer.getBufferLength(); + int position = 0; + while (position < data.length) + { + if (data[position++] == '<') + { + if (position < data.length && data[position] == '!') + { + // Ignore doctype + continue; + } + if (compareLowerCase(data, length, position, "html")) + { + // It's an HTML page, handle HTML pages + return parseHtmlPage(buffer, position); + } + else + { + // The whole thing is the body. + return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, 0, length), null); + } + } + } + // If we're here, we mustn't have found a tag + return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, 0, length), null); + } + + private Page parseHtmlPage(SitemeshBuffer buffer, int position) + { + char[] data = buffer.getCharArray(); + int length = buffer.getBufferLength(); + int bodyStart = -1; + int bodyLength = -1; + int headStart = -1; + int headLength = -1; + // Find head end and start, and body start + Map bodyProperties = null; + while (position < length) + { + if (data[position++] == '<') + { + if (compareLowerCase(data, length, position, "head")) + { + position = findEndOf(data, length, position + 4, ">"); + headStart = position; + // Find end of head + position = findStartOf(data, length, position, ""); + headLength = position - headStart; + position += 7; + } + else if (compareLowerCase(data, length, position, "body")) + { + HashSimpleMap map = new HashSimpleMap(); + bodyStart = parseProperties(data, length, position + 4, map); + bodyProperties = map.getMap(); + break; + } + } + } + + if (bodyStart < 0) + { + // No body found + bodyStart = length; + bodyLength = 0; + } + else + { + for (int i = length - 8; i > bodyStart; i--) + { + if (compareLowerCase(data, length, i, "")) + { + bodyLength = i - bodyStart; + break; + } + } + if (bodyLength == -1) + { + bodyLength = length - bodyStart; + } + } + + if (headLength > 0) + { + int idx = headStart; + int headEnd = headStart + headLength; + String title = null; + + TreeMap deletions = new TreeMap(); + + // Extract meta attributes out of head + Map metaAttributes = new HashMap(); + while (idx < headEnd) + { + if (data[idx++] == '<') + { + if (compareLowerCase(data, headEnd, idx, "meta")) + { + MetaTagSimpleMap map = new MetaTagSimpleMap(); + idx = parseProperties(data, headEnd, idx + 4, map); + if (map.getName() != null && map.getContent() != null) + { + metaAttributes.put(map.getName(), map.getContent()); + } + } + } + } + + // We need a new head buffer because we have to remove the title and content tags from it + Map pageProperties = new HashMap(); + for (int i = headStart; i < headEnd; i++) + { + char c = data[i]; + if (c == '<') + { + if (compareLowerCase(data, headEnd, i + 1, "title")) + { + int titleStart = findEndOf(data, headEnd, i + 6, ">"); + int titleEnd = findStartOf(data, headEnd, titleStart, "<"); + title = new String(data, titleStart, titleEnd - titleStart); + int titleTagEnd = titleEnd + "".length(); + deletions.put(i, titleTagEnd - i); + i = titleTagEnd - 1; + } + else if (compareLowerCase(data, headEnd, i + 1, "content")) + { + ContentTagSimpleMap map = new ContentTagSimpleMap(); + int contentStart = parseProperties(data, headEnd, i + 8, map); + int contentEnd = findStartOf(data, headEnd, contentStart, ""); + pageProperties.put(map.getTag(), new String(data, contentStart, contentEnd - contentStart)); + int contentTagEnd = contentEnd + "".length(); + deletions.put(i, contentTagEnd - i); + i = contentTagEnd - 1; + } + } + } + + return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, bodyStart, bodyLength), bodyProperties, + new SitemeshBufferFragment(buffer, headStart, headEnd - headStart, deletions), title, metaAttributes, pageProperties); + } + else + { + return new PartialPageParserHtmlPage(buffer, new SitemeshBufferFragment(buffer, bodyStart, bodyLength), bodyProperties); + } + } + + private static boolean compareLowerCase(final char[] data, final int dataEnd, int position, String token) + { + int l = position + token.length(); + if (l > dataEnd) + { + return false; + } + for (int i = 0; i < token.length(); i++) + { + // | 32 converts from ASCII uppercase to ASCII lowercase + char potential = data[position + i]; + char needed = token.charAt(i); + if ((Character.isLetter(potential) && (potential | 32) != needed) || potential != needed) + { + return false; + } + } + return true; + } + + private static int findEndOf(final char[] data, final int dataEnd, int position, String token) + { + for (int i = position; i < dataEnd - token.length(); i++) + { + if (compareLowerCase(data, dataEnd, i, token)) + { + return i + token.length(); + } + } + return dataEnd; + } + + private static int findStartOf(final char[] data, final int dataEnd, int position, String token) + { + for (int i = position; i < dataEnd - token.length(); i++) + { + if (compareLowerCase(data, dataEnd, i, token)) + { + return i; + } + } + return dataEnd; + } + + /** + * Parse the properties of the current tag + * + * @param data the data + * @param dataEnd the end index of the data + * @param position our position in the data, this should be the first character after the tag name + * @param map to the map to parse the properties into + * + * @return The position of the first character after the tag + */ + private static int parseProperties(char[] data, int dataEnd, int position, SimpleMap map) + { + int idx = position; + + while (idx < dataEnd) + { + // Skip forward to the next non-whitespace character + while (idx < dataEnd && Character.isWhitespace(data[idx])) + { + idx++; + } + + // Make sure its not the end of the data or the end of the tag + if (idx == dataEnd || data[idx] == '>' || data[idx] == '/') + { + break; + } + + int startAttr = idx; + + // Find the next equals + while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '=' && data[idx] != '>') + { + idx++; + } + + if (idx == dataEnd || data[idx] != '=') + { + continue; + } + + String attrName = new String(data, startAttr, idx - startAttr); + + idx++; + if (idx == dataEnd) + { + break; + } + + int startValue = idx; + int endValue; + if (data[idx] == '"') + { + idx++; + startValue = idx; + while (idx < dataEnd && data[idx] != '"') + { + idx++; + } + if (idx == dataEnd) + { + break; + } + endValue = idx; + idx++; + } + else if (data[idx] == '\'') + { + idx++; + startValue = idx; + while (idx < dataEnd && data[idx] != '\'') + { + idx++; + } + if (idx == dataEnd) + { + break; + } + endValue = idx; + idx++; + } + else + { + while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '/' && data[idx] != '>') + { + idx++; + } + endValue = idx; + } + String attrValue = new String(data, startValue, endValue - startValue); + map.put(attrName, attrValue); + } + // Find the end of the tag + while (idx < dataEnd && data[idx] != '>') + { + idx++; + } + if (idx == dataEnd) + { + return idx; + } + else + { + // Return the first character after the end of the tag + return idx + 1; + } + } + + public static interface SimpleMap + { + public void put(String key, String value); + } + + public static class MetaTagSimpleMap implements SimpleMap + { + private String name; + private String content; + + public void put(String key, String value) + { + if (key.equals("name")) + { + name = value; + } + else if (key.equals("content")) + { + content = value; + } + } + + public String getName() + { + return name; + } + + public String getContent() + { + return content; + } + } + + public static class ContentTagSimpleMap implements SimpleMap + { + private String tag; + + public void put(String key, String value) + { + if (key.equals("tag")) + { + tag = value; + } + } + + public String getTag() + { + return tag; + } + } + + public static class HashSimpleMap implements SimpleMap + { + private final Map map = new HashMap(); + + public void put(String key, String value) + { + map.put(key, value); + } + + public Map getMap() + { + return map; + } + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserHtmlPage.java similarity index 59% rename from src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java rename to src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserHtmlPage.java index 235eb07a..cb434cbe 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastHtmlPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserHtmlPage.java @@ -2,35 +2,36 @@ import com.opensymphony.module.sitemesh.HTMLPage; import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import java.io.IOException; +import java.io.StringWriter; import java.io.Writer; import java.util.Map; -public class SuperFastHtmlPage extends SuperFastPage implements HTMLPage +public class PartialPageParserHtmlPage extends PartialPageParserPage implements HTMLPage { - private final char[] head; + private final SitemeshBufferFragment head; - public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map bodyProperties) + public PartialPageParserHtmlPage(SitemeshBuffer sitemeshBuffer, SitemeshBufferFragment body, Map bodyProperties) { - this(sitemeshBuffer, bodyStart, bodyLength, bodyProperties, null, null, null, null); + this(sitemeshBuffer, body, bodyProperties, null, null, null, null); } /** * * @param sitemeshBuffer The buffer for the page - * @param bodyStart The start of the body - * @param bodyLength The length of the body + * @param body The body fragment * @param bodyProperties The properties of the body * @param head The head section * @param title The title * @param metaAttributes The meta attributes found in the head section * @param pageProperties The page properties extracted from the head section */ - public SuperFastHtmlPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength, Map bodyProperties, - char[] head, String title, Map metaAttributes, Map pageProperties) + public PartialPageParserHtmlPage(SitemeshBuffer sitemeshBuffer, SitemeshBufferFragment body, Map bodyProperties, + SitemeshBufferFragment head, String title, Map metaAttributes, Map pageProperties) { - super(sitemeshBuffer, bodyStart, bodyLength); + super(sitemeshBuffer, body); this.head = head; if (title == null) { @@ -57,7 +58,7 @@ public void writeHead(Writer out) throws IOException { if (head != null) { - out.write(head); + head.writeTo(out); } } @@ -65,7 +66,13 @@ public String getHead() { if (head != null) { - return new String(head); + StringWriter headString = new StringWriter(); + try { + head.writeTo(headString); + } catch (IOException e) { + throw new RuntimeException("IOException occured while writing to buffer?"); + } + return headString.toString(); } else { diff --git a/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserPage.java b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserPage.java new file mode 100644 index 00000000..4830b2cf --- /dev/null +++ b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParserPage.java @@ -0,0 +1,29 @@ +package com.opensymphony.module.sitemesh.parser; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; +import com.opensymphony.module.sitemesh.SitemeshWriter; + +public class PartialPageParserPage extends AbstractPage { + + private final SitemeshBufferFragment body; + + public PartialPageParserPage(SitemeshBuffer sitemeshBuffer, SitemeshBufferFragment body) { + super(sitemeshBuffer); + this.body = body; + } + + @Override + public void writeBody(Writer out) throws IOException { + if (out instanceof SitemeshWriter) { + ((SitemeshWriter) out).writeSitemeshBufferFragment(body); + } else { + body.writeTo(out); + } + } +} diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java deleted file mode 100644 index 09409b0c..00000000 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastPage.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.opensymphony.module.sitemesh.parser; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; - -import com.opensymphony.module.sitemesh.SitemeshBuffer; -import com.opensymphony.module.sitemesh.SitemeshWriter; - -public class SuperFastPage extends AbstractPage { - - private final SitemeshBuffer sitemeshBuffer; - private final int bodyStart; - private final int bodyLength; - - - public SuperFastPage(SitemeshBuffer sitemeshBuffer, int bodyStart, int bodyLength) { - this.sitemeshBuffer = sitemeshBuffer; - this.bodyStart = bodyStart; - this.bodyLength = bodyLength; - this.pageData = sitemeshBuffer.getCharArray(); - } - - @Override - public void writePage(Writer out) throws IOException { - sitemeshBuffer.writeTo(out, 0, sitemeshBuffer.getBufferLength()); - } - - @Override - public int getContentLength() { - // We encode it but not into a new buffer - CountingOutputStream counter = new CountingOutputStream(); - try - { - OutputStreamWriter writer = new OutputStreamWriter(counter); - writePage(writer); - // We mush flush, because the writer will buffer - writer.flush(); - } catch (IOException ioe) { - // Ignore, it's not possible with our OutputStream - } - return counter.getCount(); - } - - @Override - public void writeBody(Writer out) throws IOException { - if (out instanceof SitemeshWriter) { - ((SitemeshWriter) out).writeSitemeshBuffer(sitemeshBuffer, bodyStart, bodyLength); - } else { - sitemeshBuffer.writeTo(out, bodyStart, bodyLength); - } - } - - protected static class CountingOutputStream extends OutputStream { - private int count = 0; - - @Override - public void write(int i) throws IOException { - count++; - } - - public int getCount() { - return count; - } - } -} diff --git a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java index f4916e8d..17473ea3 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/SuperFastSimplePageParser.java @@ -1,391 +1,8 @@ package com.opensymphony.module.sitemesh.parser; -import java.io.CharArrayWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import com.opensymphony.module.sitemesh.Page; -import com.opensymphony.module.sitemesh.PageParser; -import com.opensymphony.module.sitemesh.SitemeshBuffer; - /** - * Super fast page parser. It uses a single buffer, and never copies anything, apart from the title, meta attributes - * and body properties. This page parser makes several assumptions: - *

- *

  • If the first tag is an html tag, it's an HTML page, otherwise, it's a fragment, and no head/title/etc - * parsing will be done.
- * - * @since v2.4 + * @deprecated Renamed to PartialPageParser */ -public class SuperFastSimplePageParser implements PageParser -{ - public Page parse(SitemeshBuffer buffer) throws IOException - { - char[] data = buffer.getCharArray(); - int length = buffer.getBufferLength(); - int position = 0; - while (position < data.length) - { - if (data[position++] == '<') - { - if (position < data.length && data[position] == '!') - { - // Ignore doctype - continue; - } - if (compareLowerCase(data, length, position, "html")) - { - // It's an HTML page, handle HTML pages - return parseHtmlPage(buffer, position); - } - else - { - // The whole thing is the body. - return new SuperFastHtmlPage(buffer, 0, length, null); - } - } - } - // If we're here, we mustn't have found a tag - return new SuperFastHtmlPage(buffer, 0, length, null); - } - - private Page parseHtmlPage(SitemeshBuffer buffer, int position) - { - char[] data = buffer.getCharArray(); - int length = buffer.getBufferLength(); - int bodyStart = -1; - int bodyLength = -1; - int headStart = -1; - int headLength = -1; - // Find head end and start, and body start - Map bodyProperties = null; - while (position < length) - { - if (data[position++] == '<') - { - if (compareLowerCase(data, length, position, "head")) - { - position = findEndOf(data, length, position + 4, ">"); - headStart = position; - // Find end of head - position = findStartOf(data, length, position, ""); - headLength = position - headStart; - position += 7; - } - else if (compareLowerCase(data, length, position, "body")) - { - HashSimpleMap map = new HashSimpleMap(); - bodyStart = parseProperties(data, length, position + 4, map); - bodyProperties = map.getMap(); - break; - } - } - } - - if (bodyStart < 0) - { - // No body found - bodyStart = length; - bodyLength = 0; - } - else - { - for (int i = length - 8; i > bodyStart; i--) - { - if (compareLowerCase(data, length, i, "")) - { - bodyLength = i - bodyStart; - break; - } - } - if (bodyLength == -1) - { - bodyLength = length - bodyStart; - } - } - - if (headLength > 0) - { - int idx = headStart; - int headEnd = headStart + headLength; - String title = null; - - // Extract meta attributes out of head - Map metaAttributes = new HashMap(); - while (idx < headEnd) - { - if (data[idx++] == '<') - { - if (compareLowerCase(data, headEnd, idx, "meta")) - { - MetaTagSimpleMap map = new MetaTagSimpleMap(); - idx = parseProperties(data, headEnd, idx + 4, map); - if (map.getName() != null && map.getContent() != null) - { - metaAttributes.put(map.getName(), map.getContent()); - } - } - } - } - - // We need a new head buffer because we have to remove the title and content tags from it - Map pageProperties = new HashMap(); - CharArrayWriter head = new CharArrayWriter(); - for (int i = headStart; i < headEnd; i++) - { - char c = data[i]; - if (c == '<') - { - if (compareLowerCase(data, headEnd, i + 1, "title")) - { - int titleStart = findEndOf(data, headEnd, i + 6, ">"); - int titleEnd = findStartOf(data, headEnd, titleStart, "<"); - title = new String(data, titleStart, titleEnd - titleStart); - i = titleEnd + "".length() - 1; - } - else if (compareLowerCase(data, headEnd, i + 1, "content")) - { - ContentTagSimpleMap map = new ContentTagSimpleMap(); - int contentStart = parseProperties(data, headEnd, i + 8, map); - int contentEnd = findStartOf(data, headEnd, contentStart, ""); - pageProperties.put(map.getTag(), new String(data, contentStart, contentEnd - contentStart)); - i = contentEnd + "".length() - 1; - } - else - { - head.append(c); - } - } - else - { - head.append(c); - } - } - - return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties, head.toCharArray(), title, metaAttributes, pageProperties); - } - else - { - return new SuperFastHtmlPage(buffer, bodyStart, bodyLength, bodyProperties); - } - } - - private static boolean compareLowerCase(final char[] data, final int dataEnd, int position, String token) - { - int l = position + token.length(); - if (l > dataEnd) - { - return false; - } - for (int i = 0; i < token.length(); i++) - { - // | 32 converts from ASCII uppercase to ASCII lowercase - char potential = data[position + i]; - char needed = token.charAt(i); - if ((Character.isLetter(potential) && (potential | 32) != needed) || potential != needed) - { - return false; - } - } - return true; - } - - private static int findEndOf(final char[] data, final int dataEnd, int position, String token) - { - for (int i = position; i < dataEnd - token.length(); i++) - { - if (compareLowerCase(data, dataEnd, i, token)) - { - return i + token.length(); - } - } - return dataEnd; - } - - private static int findStartOf(final char[] data, final int dataEnd, int position, String token) - { - for (int i = position; i < dataEnd - token.length(); i++) - { - if (compareLowerCase(data, dataEnd, i, token)) - { - return i; - } - } - return dataEnd; - } - - /** - * Parse the properties of the current tag - * - * @param data the data - * @param dataEnd the end index of the data - * @param position our position in the data, this should be the first character after the tag name - * @param map to the map to parse the properties into - * - * @return The position of the first character after the tag - */ - private static int parseProperties(char[] data, int dataEnd, int position, SimpleMap map) - { - int idx = position; - - while (idx < dataEnd) - { - // Skip forward to the next non-whitespace character - while (idx < dataEnd && Character.isWhitespace(data[idx])) - { - idx++; - } - - // Make sure its not the end of the data or the end of the tag - if (idx == dataEnd || data[idx] == '>' || data[idx] == '/') - { - break; - } - - int startAttr = idx; - - // Find the next equals - while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '=' && data[idx] != '>') - { - idx++; - } - - if (idx == dataEnd || data[idx] != '=') - { - continue; - } - - String attrName = new String(data, startAttr, idx - startAttr); - - idx++; - if (idx == dataEnd) - { - break; - } - - int startValue = idx; - int endValue; - if (data[idx] == '"') - { - idx++; - startValue = idx; - while (idx < dataEnd && data[idx] != '"') - { - idx++; - } - if (idx == dataEnd) - { - break; - } - endValue = idx; - idx++; - } - else if (data[idx] == '\'') - { - idx++; - startValue = idx; - while (idx < dataEnd && data[idx] != '\'') - { - idx++; - } - if (idx == dataEnd) - { - break; - } - endValue = idx; - idx++; - } - else - { - while (idx < dataEnd && !Character.isWhitespace(data[idx]) && data[idx] != '/' && data[idx] != '>') - { - idx++; - } - endValue = idx; - } - String attrValue = new String(data, startValue, endValue - startValue); - map.put(attrName, attrValue); - } - // Find the end of the tag - while (idx < dataEnd && data[idx] != '>') - { - idx++; - } - if (idx == dataEnd) - { - return idx; - } - else - { - // Return the first character after the end of the tag - return idx + 1; - } - } - - public static interface SimpleMap - { - public void put(String key, String value); - } - - public static class MetaTagSimpleMap implements SimpleMap - { - private String name; - private String content; - - public void put(String key, String value) - { - if (key.equals("name")) - { - name = value; - } - else if (key.equals("content")) - { - content = value; - } - } - - public String getName() - { - return name; - } - - public String getContent() - { - return content; - } - } - - public static class ContentTagSimpleMap implements SimpleMap - { - private String tag; - - public void put(String key, String value) - { - if (key.equals("tag")) - { - tag = value; - } - } - - public String getTag() - { - return tag; - } - } - - public static class HashSimpleMap implements SimpleMap - { - private final Map map = new HashMap(); - - public void put(String key, String value) - { - map.put(key, value); - } - - public Map getMap() - { - return map; - } - } +@Deprecated +public class SuperFastSimplePageParser extends PartialPageParser { } diff --git a/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java b/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java index 92da51cb..5ab208c4 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java @@ -1,5 +1,8 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; +import com.opensymphony.module.sitemesh.SitemeshWriter; import com.opensymphony.module.sitemesh.html.util.CharArray; import com.opensymphony.module.sitemesh.html.rules.PageBuilder; @@ -17,22 +20,36 @@ */ public class TokenizedHTMLPage extends AbstractHTMLPage implements PageBuilder { - private CharArray body; - private CharArray head; + private SitemeshBufferFragment body; + private SitemeshBufferFragment head; - public TokenizedHTMLPage(char[] original, CharArray body, CharArray head) { - this.pageData = original; + public TokenizedHTMLPage(SitemeshBuffer sitemeshBuffer) { + super(sitemeshBuffer); + addProperty("title", ""); + } + + public void setBody(SitemeshBufferFragment body) { this.body = body; + } + + public void setHead(SitemeshBufferFragment head) { this.head = head; - addProperty("title", ""); } public void writeHead(Writer out) throws IOException { - out.write(head.toString()); + if (out instanceof SitemeshWriter) { + ((SitemeshWriter) out).writeSitemeshBufferFragment(head); + } else { + head.writeTo(out); + } } public void writeBody(Writer out) throws IOException { - out.write(body.toString()); + if (out instanceof SitemeshWriter) { + ((SitemeshWriter) out).writeSitemeshBufferFragment(body); + } else { + body.writeTo(out); + } } public String getHead() { @@ -42,9 +59,4 @@ public String getHead() { public String getBody() { return body.toString(); } - - public String getPage() { - return new String(pageData); - } - } diff --git a/src/parser-tests/parsers.properties b/src/parser-tests/parsers.properties index 05246765..c8435edb 100644 --- a/src/parser-tests/parsers.properties +++ b/src/parser-tests/parsers.properties @@ -4,5 +4,5 @@ parser.html.tests=src/parser-tests parser.fast.class=com.opensymphony.module.sitemesh.parser.FastPageParser parser.fast.tests=src/parser-tests/superfast -parser.superfast.class=com.opensymphony.module.sitemesh.parser.SuperFastSimplePageParser +parser.superfast.class=com.opensymphony.module.sitemesh.parser.PartialPageParser parser.superfast.tests=src/parser-tests/superfast \ No newline at end of file diff --git a/src/parser-tests/test10.txt b/src/parser-tests/test10.txt index 9e02a551..70859f34 100644 --- a/src/parser-tests/test10.txt +++ b/src/parser-tests/test10.txt @@ -1,13 +1,10 @@ ~~~ INPUT ~~~ - < > page title second title - THIS IS WRONG - < ><> - < ><>

PAGE BODY

diff --git a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java index 55ec310f..30e3eed6 100644 --- a/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java +++ b/src/test/com/opensymphony/module/sitemesh/chaining/ChainingBufferTest.java @@ -2,6 +2,7 @@ import java.io.CharArrayWriter; import java.util.Arrays; +import java.util.TreeMap; import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.SitemeshBuffer; @@ -14,41 +15,44 @@ */ public class ChainingBufferTest extends TestCase { public void testSimpleChain() throws Exception { - SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + SitemeshBuffer buffer = newSitemeshBuffer("1234", 2, newBufferFragment("ab")); assertEquals("12ab34", getContent(buffer)); + assertCorrectLength(buffer); } public void testBefore() throws Exception { - SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + SitemeshBuffer buffer = newSitemeshBuffer("1234", 2, newBufferFragment("ab")); assertEquals("1", getContent(buffer, 0, 1)); - assertEquals("12", getContent(buffer, 0, 2)); + assertEquals("12ab", getContent(buffer, 0, 2)); } public void testAfter() throws Exception { - SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("ab", 2)); + SitemeshBuffer buffer = newSitemeshBuffer("1234", 2, newBufferFragment("ab")); assertEquals("ab34", getContent(buffer, 2, 2)); assertEquals("4", getContent(buffer, 3, 1)); } public void testFragment() throws Exception { - SitemeshBuffer buffer = newSitemeshBuffer("1234", newBufferFragment("abcd", 1, 2, 2)); + SitemeshBuffer buffer = newSitemeshBuffer("1234", 2, newBufferFragment("abcd", 1, 2)); assertEquals("12bc34", getContent(buffer)); + assertCorrectLength(buffer); } public void testDeepFragments() throws Exception { SitemeshBuffer buffer = newSitemeshBuffer("123456789", - newBufferFragment("abcdefg", 3, - newBufferFragment("hijklm", 1, 1, 4), - newBufferFragment("nopqr", 1, 4, 5)), - newBufferFragment("tuzwx", 0, 2, 8)); + 3, newBufferFragment("abcdefg", + 4, newBufferFragment("hijklm", 1, 1), + 5, newBufferFragment("nopqr", 1, 4)), + 8, newBufferFragment("tuzwx", 0, 2)); assertEquals("123abcdieopqrfg45678tu9", getContent(buffer)); + assertCorrectLength(buffer); } public void testWriter() throws Exception { SitemeshBuffer buffer = newSitemeshBuffer("123456"); SitemeshBufferWriter writer = new SitemeshBufferWriter(); writer.write("abc"); - writer.writeSitemeshBuffer(buffer, 1, 4); + writer.writeSitemeshBufferFragment(new SitemeshBufferFragment(buffer, 1, 4)); writer.write("def"); assertEquals("abcdef", writer.toString()); assertEquals("abc2345def", getContent(writer.getSitemeshBuffer())); @@ -66,19 +70,40 @@ private String getContent(SitemeshBuffer buffer, int start, int length) throws E return writer.toString(); } - private SitemeshBuffer newSitemeshBuffer(String content, SitemeshBufferFragment... fragments) { - return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), Arrays.asList(fragments)); + private void assertCorrectLength(SitemeshBuffer buffer) throws Exception { + assertEquals(getContent(buffer).length(), buffer.getTotalLength()); } - private SitemeshBufferFragment newBufferFragment(String content, int position) { - return new SitemeshBufferFragment(newSitemeshBuffer(content), 0, content.length(), position); + private SitemeshBuffer newSitemeshBuffer(String content) { + return new DefaultSitemeshBuffer(content.toCharArray(), content.length()); } - private SitemeshBufferFragment newBufferFragment(String content, int start, int length, int position) { - return new SitemeshBufferFragment(newSitemeshBuffer(content), start, length, position); + private SitemeshBuffer newSitemeshBuffer(String content, int pos1, SitemeshBufferFragment frag1) { + TreeMap fragments = new TreeMap(); + fragments.put(pos1, frag1); + return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), fragments); } - private SitemeshBufferFragment newBufferFragment(String content, int position, SitemeshBufferFragment... fragments) { - return new SitemeshBufferFragment(newSitemeshBuffer(content, fragments), 0, content.length(), position); + private SitemeshBuffer newSitemeshBuffer(String content, int pos1, SitemeshBufferFragment frag1, int pos2, SitemeshBufferFragment frag2) { + TreeMap fragments = new TreeMap(); + fragments.put(pos1, frag1); + fragments.put(pos2, frag2); + return new DefaultSitemeshBuffer(content.toCharArray(), content.length(), fragments); + } + + private SitemeshBufferFragment newBufferFragment(String content) { + return new SitemeshBufferFragment(newSitemeshBuffer(content), 0, content.length()); + } + + private SitemeshBufferFragment newBufferFragment(String content, int start, int length) { + return new SitemeshBufferFragment(newSitemeshBuffer(content), start, length); + } + + private SitemeshBufferFragment newBufferFragment(String content, int pos1, SitemeshBufferFragment frag1) { + return new SitemeshBufferFragment(newSitemeshBuffer(content, pos1, frag1), 0, content.length()); + } + + private SitemeshBufferFragment newBufferFragment(String content, int pos1, SitemeshBufferFragment frag1, int pos2, SitemeshBufferFragment frag2) { + return new SitemeshBufferFragment(newSitemeshBuffer(content, pos1, frag1, pos2, frag2), 0, content.length()); } } diff --git a/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java b/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java index c9ba24e7..06c08695 100644 --- a/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java +++ b/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java @@ -1,21 +1,27 @@ package com.opensymphony.module.sitemesh.html; -import com.opensymphony.module.sitemesh.html.util.CharArray; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.rules.TagReplaceRule; import junit.framework.TestCase; -import java.io.Reader; -import java.io.StringReader; -import java.io.Writer; -import java.io.StringWriter; import java.io.IOException; public class HTMLProcessorTest extends TestCase { - public void testCreatesStateTransitionEvent() throws IOException { - char[] input = "".toCharArray(); - HTMLProcessor htmlProcessor = new HTMLProcessor(input, new CharArray(128)); + private SitemeshBufferFragment.Builder body; + + private HTMLProcessor createProcessor(String input) { + SitemeshBuffer buffer = new DefaultSitemeshBuffer(input.toCharArray()); + body = SitemeshBufferFragment.builder().setBuffer(buffer); + return new HTMLProcessor(buffer, body); + } + + public void testCreatesStateTransitionEvent() throws IOException { + String input = ""; + HTMLProcessor htmlProcessor = createProcessor(input); State defaultState = htmlProcessor.defaultState(); @@ -32,42 +38,34 @@ public void stateFinished() { } public void testSupportsConventionalReaderAndWriter() throws IOException { - Reader in = new StringReader("world"); - Writer out = new StringWriter(); - - HTMLProcessor processor = new HTMLProcessor(in, out); + HTMLProcessor processor = createProcessor("world"); processor.addRule(new TagReplaceRule("b", "strong")); processor.process(); - assertEquals("world", out.toString()); + assertEquals("world", body.build().toString()); } public void testAllowsRulesToModifyAttributes() throws IOException { - Reader in = new StringReader("world"); - Writer out = new StringWriter(); - - HTMLProcessor processor = new HTMLProcessor(in, out); + HTMLProcessor processor = createProcessor("world"); processor.addRule(new BasicRule("a") { public void process(Tag tag) { + currentBuffer().delete(tag.getPosition(), tag.getLength()); CustomTag customTag = new CustomTag(tag); String href = customTag.getAttributeValue("href", false); if (href != null) { href = href.toUpperCase(); customTag.setAttributeValue("href", true, href); } - customTag.writeTo(currentBuffer()); + customTag.writeTo(currentBuffer(), tag.getPosition()); } }); processor.process(); - assertEquals("world", out.toString()); + assertEquals("world", body.build().toString()); } public void testSupportsChainedFilteringOfTextContent() throws IOException { - Reader in = new StringReader("world"); - Writer out = new StringWriter(); - - HTMLProcessor processor = new HTMLProcessor(in, out); + HTMLProcessor processor = createProcessor("world"); processor.addTextFilter(new TextFilter() { public String filter(String text) { return text.toUpperCase(); @@ -80,15 +78,11 @@ public String filter(String text) { }); processor.process(); - assertEquals("WoRLD", out.toString()); + assertEquals("WoRLD", body.build().toString()); } public void testSupportsTextFiltersForSpecificStates() throws IOException { - Reader in = new StringReader("la la
la la laaaa
laaaa
la la"); - Writer out = new StringWriter(); - - HTMLProcessor processor = new HTMLProcessor(in, out); - + HTMLProcessor processor = createProcessor("la la
la la laaaa
laaaa
la la"); State capsState = new State(); processor.addRule(new StateTransitionRule("capitalism", capsState, true)); @@ -99,13 +93,12 @@ public String filter(String text) { }); processor.process(); - assertEquals("la la
la la LAAAA
LAAAA
la la", out.toString()); + assertEquals("la la
la la LAAAA
LAAAA
la la", body.build().toString()); } public void testCanAddAttributesToCustomTag() throws IOException { - CharArray buffer = new CharArray(64); String html = "

Headline

"; - HTMLProcessor htmlProcessor = new HTMLProcessor(html.toCharArray(), buffer); + HTMLProcessor htmlProcessor = createProcessor(html); htmlProcessor.addRule(new BasicRule() { public boolean shouldProcess(String tag) { return tag.equalsIgnoreCase("h1"); @@ -113,15 +106,15 @@ public boolean shouldProcess(String tag) { public void process(Tag tag) { if (tag.getType() == Tag.OPEN) { + currentBuffer().delete(tag.getPosition(), tag.getLength()); CustomTag ctag = new CustomTag(tag); ctag.addAttribute("class", "y"); assertEquals(1, ctag.getAttributeCount()); - tag = ctag; + ctag.writeTo(currentBuffer(), tag.getPosition()); } - tag.writeTo(currentBuffer()); } }); htmlProcessor.process(); - assertEquals("

Headline

", buffer.toString()); + assertEquals("

Headline

", body.build().toString()); } } diff --git a/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java b/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java index af0376cc..3597f48b 100644 --- a/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java +++ b/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java @@ -1,5 +1,8 @@ package com.opensymphony.module.sitemesh.html.rules; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBuffer; +import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import junit.framework.TestCase; import java.io.Reader; @@ -12,22 +15,24 @@ public class RegexReplacementTextFilterTest extends TestCase { - public void testReplacesTextContentMatchedByRegularExpression() throws IOException { - Reader in = new StringReader("Today is DATE so hi"); - Writer out = new StringWriter(); + private SitemeshBufferFragment.Builder body; + + private HTMLProcessor createProcessor(String input) { + SitemeshBuffer buffer = new DefaultSitemeshBuffer(input.toCharArray()); + body = SitemeshBufferFragment.builder().setBuffer(buffer); + return new HTMLProcessor(buffer, body); + } - HTMLProcessor processor = new HTMLProcessor(in, out); + public void testReplacesTextContentMatchedByRegularExpression() throws IOException { + HTMLProcessor processor = createProcessor("Today is DATE so hi"); processor.addTextFilter(new RegexReplacementTextFilter("DATE", "1-jan-2009")); processor.process(); - assertEquals("Today is 1-jan-2009 so hi", out.toString()); + assertEquals("Today is 1-jan-2009 so hi", body.build().toString()); } public void testAllowsMatchedGroupToBeUsedInSubsitution() throws IOException { - Reader in = new StringReader("I think JIRA:SIM-1234 is the way forward"); - Writer out = new StringWriter(); - - HTMLProcessor processor = new HTMLProcessor(in, out); + HTMLProcessor processor = createProcessor("I think JIRA:SIM-1234 is the way forward"); processor.addTextFilter(new RegexReplacementTextFilter( "JIRA:([A-Z]+\\-[0-9]+)", "$1")); @@ -35,7 +40,7 @@ public void testAllowsMatchedGroupToBeUsedInSubsitution() throws IOException { processor.process(); assertEquals( "I think SIM-1234 is the way forward", - out.toString()); + body.build().toString()); } } diff --git a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java index aa9796f6..bdf48c54 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/ParserPerformanceComparison.java @@ -65,7 +65,7 @@ public static void main(String... args) throws Exception // Create the parsers PageParser normal = new HTMLPageParser(); PageParser fast = new FastPageParser(); - PageParser superfast = new SuperFastSimplePageParser(); + PageParser superfast = new PartialPageParser(); System.out.println("Amount of data: " + page.length); System.gc(); From 7a6863ca5d86b8ce3f35b372e53c9cccf2f61923 Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 5 Jul 2011 19:59:02 +1000 Subject: [PATCH 11/20] Removed imports that broke stuff --- pom.xml | 4 ++-- .../sitemesh/ContentProcessor.java | 1 - .../PageParser2ContentProcessor.java | 1 - .../webapp/ContentBufferingResponse.java | 1 - target/pom-transformed.xml | 22 +++++++++++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 target/pom-transformed.xml diff --git a/pom.xml b/pom.xml index 6fc7b137..15dfed8a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ opensymphony sitemesh - 2.4.2-atlassian-2 + 2.5-atlassian-1 jar SiteMesh @@ -19,4 +19,4 @@ https://github.com/atlassian/sitemesh2 - \ No newline at end of file + diff --git a/src/java/com/opensymphony/sitemesh/ContentProcessor.java b/src/java/com/opensymphony/sitemesh/ContentProcessor.java index 11317860..f329498b 100644 --- a/src/java/com/opensymphony/sitemesh/ContentProcessor.java +++ b/src/java/com/opensymphony/sitemesh/ContentProcessor.java @@ -1,7 +1,6 @@ package com.opensymphony.sitemesh; import com.opensymphony.module.sitemesh.SitemeshBuffer; -import com.opensymphony.module.sitemesh.filter.BufferedContent; import java.io.IOException; diff --git a/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java b/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java index ad965517..9db6e344 100644 --- a/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java +++ b/src/java/com/opensymphony/sitemesh/compatability/PageParser2ContentProcessor.java @@ -1,7 +1,6 @@ package com.opensymphony.sitemesh.compatability; import com.opensymphony.module.sitemesh.*; -import com.opensymphony.module.sitemesh.filter.BufferedContent; import com.opensymphony.module.sitemesh.filter.HttpContentType; import com.opensymphony.sitemesh.Content; import com.opensymphony.sitemesh.SiteMeshContext; diff --git a/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java b/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java index 0f16afaa..29f86472 100644 --- a/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java +++ b/src/java/com/opensymphony/sitemesh/webapp/ContentBufferingResponse.java @@ -1,7 +1,6 @@ package com.opensymphony.sitemesh.webapp; import com.opensymphony.module.sitemesh.SitemeshBuffer; -import com.opensymphony.module.sitemesh.filter.BufferedContent; import com.opensymphony.module.sitemesh.filter.PageResponseWrapper; import com.opensymphony.module.sitemesh.PageParserSelector; import com.opensymphony.module.sitemesh.PageParser; diff --git a/target/pom-transformed.xml b/target/pom-transformed.xml new file mode 100644 index 00000000..15dfed8a --- /dev/null +++ b/target/pom-transformed.xml @@ -0,0 +1,22 @@ + + 4.0.0 + + + + opensymphony + sitemesh + 2.5-atlassian-1 + + jar + SiteMesh + Atlassian's fork of SiteMesh + https://github.com/atlassian/sitemesh2 + + + From 91035a96f2defd1e24b31801fb5bb83338874be3 Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 5 Jul 2011 20:00:47 +1000 Subject: [PATCH 12/20] Preparing for release of 2.5-atlassian-1 --- pom.xml | 4 ++-- target/pom-transformed.xml | 22 ---------------------- 2 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 target/pom-transformed.xml diff --git a/pom.xml b/pom.xml index 15dfed8a..6d65e1ae 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ opensymphony diff --git a/target/pom-transformed.xml b/target/pom-transformed.xml deleted file mode 100644 index 15dfed8a..00000000 --- a/target/pom-transformed.xml +++ /dev/null @@ -1,22 +0,0 @@ - - 4.0.0 - - - - opensymphony - sitemesh - 2.5-atlassian-1 - - jar - SiteMesh - Atlassian's fork of SiteMesh - https://github.com/atlassian/sitemesh2 - - - From dab543008f4d7ebd4ba8fcdd689ec118d4a404da Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 5 Jul 2011 20:31:47 +1000 Subject: [PATCH 13/20] Updated version --- build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.properties b/build.properties index 380d6f6b..e0c625c5 100644 --- a/build.properties +++ b/build.properties @@ -18,4 +18,4 @@ compile.nowarn = off Name = OpenSymphony SiteMesh name = sitemesh -version = 2.4.2 +version = 2.5-atlassian-1 From 8bbb5502ebb75fd6fa815d2b83e5752fcf2f9e4f Mon Sep 17 00:00:00 2001 From: James Roper Date: Mon, 11 Jul 2011 17:14:45 +1000 Subject: [PATCH 14/20] Readded deprecated parse() method to PageParser, and added getStringContent() method to buffer fragments so that toString() could safely be used in other contexts --- .../module/sitemesh/DefaultSitemeshBuffer.java | 12 +++++++++++- .../opensymphony/module/sitemesh/PageParser.java | 16 +++++++++++++++- .../module/sitemesh/SitemeshBufferFragment.java | 16 ++++++++++++++-- .../module/sitemesh/html/BasicRule.java | 2 +- .../module/sitemesh/html/CustomTag.java | 2 +- .../MultipassReplacementPageParser.java | 5 +++++ .../module/sitemesh/parser/FastPageParser.java | 6 ++++++ .../module/sitemesh/parser/HTMLPageParser.java | 5 +++++ .../sitemesh/parser/PartialPageParser.java | 4 ++++ .../sitemesh/parser/TokenizedHTMLPage.java | 4 ++-- .../module/sitemesh/html/HTMLProcessorTest.java | 13 +++++++------ .../rules/RegexReplacementTextFilterTest.java | 4 ++-- .../sitemesh/parser/HTMLPageParserTest.java | 2 +- 13 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java index 3edae4be..be35aa75 100644 --- a/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java +++ b/src/java/com/opensymphony/module/sitemesh/DefaultSitemeshBuffer.java @@ -87,7 +87,11 @@ public static Builder builder() { } public static Builder builder(SitemeshBuffer sitemeshBuffer) { - return new Builder((DefaultSitemeshBuffer) sitemeshBuffer); + if (sitemeshBuffer instanceof DefaultSitemeshBuffer) { + return new Builder((DefaultSitemeshBuffer) sitemeshBuffer); + } else { + return new Builder(sitemeshBuffer); + } } public static class Builder { @@ -105,6 +109,12 @@ private Builder(DefaultSitemeshBuffer buffer) { this.fragments = new TreeMap(buffer.bufferFragments); } + private Builder(SitemeshBuffer buffer) { + this.buffer = buffer.getCharArray(); + this.length = buffer.getBufferLength(); + this.fragments = new TreeMap(); + } + public Builder setBuffer(char[] buffer) { this.buffer = buffer; return this; diff --git a/src/java/com/opensymphony/module/sitemesh/PageParser.java b/src/java/com/opensymphony/module/sitemesh/PageParser.java index e201df65..459df1a4 100644 --- a/src/java/com/opensymphony/module/sitemesh/PageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/PageParser.java @@ -25,12 +25,26 @@ * @version $Revision: 1.2 $ */ public interface PageParser { + /** - * This builds a Page. + * Parse the given buffer into a page object. {@link DefaultSitemeshBuffer} is the appropriate implementation of + * this interface to pass in. * * @param buffer The buffer for the page. * @return The parsed page * @throws IOException if an error occurs */ Page parse(SitemeshBuffer buffer) throws IOException; + + /** + * Parse the given buffer into a Page object. + * + * @param buffer The buffer for the page. + * @return The parsed page + * @throws IOException if an error occurs + * @deprecated Use {@link PageParser#parse(SitemeshBuffer)}, to allow performance improvement such as single buffer + * parsing and buffer chaining. + */ + @Deprecated + Page parse(char[] buffer) throws IOException; } diff --git a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java index 850dda3c..12b2d3c3 100644 --- a/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java +++ b/src/java/com/opensymphony/module/sitemesh/SitemeshBufferFragment.java @@ -79,8 +79,7 @@ public int getTotalLength() { return total; } - @Override - public String toString() { + public String getStringContent() { StringWriter writer = new StringWriter(); try { writeTo(writer); @@ -90,6 +89,19 @@ public String toString() { return writer.toString(); } + @Override + public String toString() + { + return "SitemeshBufferFragment{" + + // Here we generate our own ID, because if the underlying writer is a CharArrayWriter, we'll end up + // with its entire contents, which we don't really want in this method. + "buffer=" + buffer.getClass().getName() + "@" + Integer.toHexString(hashCode()) + + ", start=" + start + + ", length=" + length + + ", deletions=" + deletions + + '}'; + } + public int getStart() { return start; } diff --git a/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java b/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java index 0f7ea707..721ee6a7 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java +++ b/src/java/com/opensymphony/module/sitemesh/html/BasicRule.java @@ -47,7 +47,7 @@ protected SitemeshBufferFragment.Builder currentBuffer() { } protected String getCurrentBufferContent() { - return context.currentBuffer().build().toString(); + return context.currentBuffer().build().getStringContent(); } } diff --git a/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java b/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java index 78df175c..41520d65 100644 --- a/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java +++ b/src/java/com/opensymphony/module/sitemesh/html/CustomTag.java @@ -67,7 +67,7 @@ public CustomTag(Tag tag) { public String getContents() { SitemeshBufferFragment.Builder buffer = SitemeshBufferFragment.builder().setBuffer(new DefaultSitemeshBuffer(new char[]{})); writeTo(buffer, 0); - return buffer.build().toString(); + return buffer.build().getStringContent(); } public void writeTo(SitemeshBufferFragment.Builder buffer, int position) { diff --git a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java index dab2ebd9..2330deda 100644 --- a/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/multipass/MultipassReplacementPageParser.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.multipass; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.SitemeshBuffer; @@ -21,6 +22,10 @@ public MultipassReplacementPageParser(Page page, HttpServletResponse response) { this.response = response; } + public Page parse(char[] buffer) throws IOException { + return parse(new DefaultSitemeshBuffer(buffer)); + } + public Page parse(SitemeshBuffer sitemeshBuffer) throws IOException { SitemeshBufferFragment.Builder builder = SitemeshBufferFragment.builder().setBuffer(sitemeshBuffer); HTMLProcessor processor = new HTMLProcessor(sitemeshBuffer, builder); diff --git a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java index ac9a0ea6..cde9d4a2 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/FastPageParser.java @@ -9,6 +9,7 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.SitemeshBuffer; @@ -85,6 +86,11 @@ public final class FastPageParser implements PageParser private static final int SLASH_BODY_HASH = 46434897; // "/body".hashCode(); private static final int CONTENT_HASH = 951530617; // "content".hashCode(); + public Page parse(char[] buffer) throws IOException + { + return parse(new DefaultSitemeshBuffer(buffer)); + } + public Page parse(SitemeshBuffer buffer) throws IOException { CharArrayReader reader = new CharArrayReader(buffer.getCharArray(), 0, buffer.getBufferLength()); diff --git a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java index 1f000537..c66f79bd 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/HTMLPageParser.java @@ -1,5 +1,6 @@ package com.opensymphony.module.sitemesh.parser; +import com.opensymphony.module.sitemesh.DefaultSitemeshBuffer; import com.opensymphony.module.sitemesh.Page; import com.opensymphony.module.sitemesh.PageParser; import com.opensymphony.module.sitemesh.SitemeshBuffer; @@ -34,6 +35,10 @@ */ public class HTMLPageParser implements PageParser { + public Page parse(char[] buffer) throws IOException { + return parse(new DefaultSitemeshBuffer(buffer)); + } + public Page parse(SitemeshBuffer buffer) throws IOException { SitemeshBufferFragment.Builder head = SitemeshBufferFragment.builder().setBuffer(buffer).setLength(0); SitemeshBufferFragment.Builder body = SitemeshBufferFragment.builder().setBuffer(buffer); diff --git a/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java index 2b14456e..6bd37823 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/PartialPageParser.java @@ -12,6 +12,10 @@ */ public class PartialPageParser implements PageParser { + public Page parse(char[] buffer) throws IOException { + return parse(new DefaultSitemeshBuffer(buffer)); + } + public Page parse(SitemeshBuffer buffer) throws IOException { char[] data = buffer.getCharArray(); diff --git a/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java b/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java index 5ab208c4..0b1180cf 100644 --- a/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java +++ b/src/java/com/opensymphony/module/sitemesh/parser/TokenizedHTMLPage.java @@ -53,10 +53,10 @@ public void writeBody(Writer out) throws IOException { } public String getHead() { - return head.toString(); + return head.getStringContent(); } public String getBody() { - return body.toString(); + return body.getStringContent(); } } diff --git a/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java b/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java index 06c08695..a9bfe364 100644 --- a/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java +++ b/src/test/com/opensymphony/module/sitemesh/html/HTMLProcessorTest.java @@ -4,6 +4,7 @@ import com.opensymphony.module.sitemesh.SitemeshBuffer; import com.opensymphony.module.sitemesh.SitemeshBufferFragment; import com.opensymphony.module.sitemesh.html.rules.TagReplaceRule; +import com.opensymphony.module.sitemesh.html.util.StringSitemeshBuffer; import junit.framework.TestCase; @@ -14,7 +15,7 @@ public class HTMLProcessorTest extends TestCase { private SitemeshBufferFragment.Builder body; private HTMLProcessor createProcessor(String input) { - SitemeshBuffer buffer = new DefaultSitemeshBuffer(input.toCharArray()); + SitemeshBuffer buffer = new StringSitemeshBuffer(input); body = SitemeshBufferFragment.builder().setBuffer(buffer); return new HTMLProcessor(buffer, body); } @@ -42,7 +43,7 @@ public void testSupportsConventionalReaderAndWriter() throws IOException { processor.addRule(new TagReplaceRule("b", "strong")); processor.process(); - assertEquals("world", body.build().toString()); + assertEquals("world", body.build().getStringContent()); } public void testAllowsRulesToModifyAttributes() throws IOException { @@ -61,7 +62,7 @@ public void process(Tag tag) { }); processor.process(); - assertEquals("world", body.build().toString()); + assertEquals("world", body.build().getStringContent()); } public void testSupportsChainedFilteringOfTextContent() throws IOException { @@ -78,7 +79,7 @@ public String filter(String text) { }); processor.process(); - assertEquals("WoRLD", body.build().toString()); + assertEquals("WoRLD", body.build().getStringContent()); } public void testSupportsTextFiltersForSpecificStates() throws IOException { @@ -93,7 +94,7 @@ public String filter(String text) { }); processor.process(); - assertEquals("la la
la la LAAAA
LAAAA
la la", body.build().toString()); + assertEquals("la la
la la LAAAA
LAAAA
la la", body.build().getStringContent()); } public void testCanAddAttributesToCustomTag() throws IOException { @@ -115,6 +116,6 @@ public void process(Tag tag) { } }); htmlProcessor.process(); - assertEquals("

Headline

", body.build().toString()); + assertEquals("

Headline

", body.build().getStringContent()); } } diff --git a/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java b/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java index 3597f48b..ed845dba 100644 --- a/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java +++ b/src/test/com/opensymphony/module/sitemesh/html/rules/RegexReplacementTextFilterTest.java @@ -28,7 +28,7 @@ public void testReplacesTextContentMatchedByRegularExpression() throws IOExcepti processor.addTextFilter(new RegexReplacementTextFilter("DATE", "1-jan-2009")); processor.process(); - assertEquals("Today is 1-jan-2009 so hi", body.build().toString()); + assertEquals("Today is 1-jan-2009 so hi", body.build().getStringContent()); } public void testAllowsMatchedGroupToBeUsedInSubsitution() throws IOException { @@ -40,7 +40,7 @@ public void testAllowsMatchedGroupToBeUsedInSubsitution() throws IOException { processor.process(); assertEquals( "I think SIM-1234 is the way forward", - body.build().toString()); + body.build().getStringContent()); } } diff --git a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java index 703adf09..09ae0997 100644 --- a/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java +++ b/src/test/com/opensymphony/module/sitemesh/parser/HTMLPageParserTest.java @@ -133,7 +133,7 @@ public void testProperties() throws Exception { String pageKey = pageKeys[i]; String blockValue = props.getProperty(pageKey); String pageValue = page.getProperty(pageKey); - assertEquals(file.getName(), + assertEquals(file.getName() + ": " + pageKey, blockValue == null ? null : blockValue.trim(), pageValue == null ? null : pageValue.trim()); } From 2ec48b2ee571806e2eca2b9c1a641b9a12206308 Mon Sep 17 00:00:00 2001 From: James Roper Date: Mon, 11 Jul 2011 17:17:46 +1000 Subject: [PATCH 15/20] Update for release of 2.5-atlassian-2 --- CHANGES.txt | 7 +++++++ build.properties | 2 +- pom.xml | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 94a1be4c..3e66f77c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +-------------------------- +-- Changes in 2.5 -- +-------------------------- + * Single buffer parsing + * Buffer chaining + * PartialPageParser that only parses the section of bodies + -------------------------- -- Changes in 2.4.2 -- -------------------------- diff --git a/build.properties b/build.properties index e0c625c5..fe3ce13b 100644 --- a/build.properties +++ b/build.properties @@ -18,4 +18,4 @@ compile.nowarn = off Name = OpenSymphony SiteMesh name = sitemesh -version = 2.5-atlassian-1 +version = 2.5-atlassian-2 diff --git a/pom.xml b/pom.xml index 6d65e1ae..f0c3ee56 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ opensymphony sitemesh - 2.5-atlassian-1 + 2.5-atlassian-2 jar SiteMesh From 4fcde51c60dfa43b60884291cb68e70851255b94 Mon Sep 17 00:00:00 2001 From: Matt Quail Date: Wed, 13 Jul 2011 17:04:37 +1000 Subject: [PATCH 16/20] Do not set the response content-length in the NoDecorator, as it is impossible to know the content-length in bytes when we don't know the encoding (sitemesh 2.4 used to assume UTF-8, but setting the content-length is not a significant advantage anyway). Therefore, remove all references to content-length as it is only used by NoDecorator. (Fixes bug JRADEV-6607) --- .gitignore | 2 ++ build.properties | 2 +- pom.xml | 3 ++- .../opensymphony/module/sitemesh/Page.java | 8 ------ .../module/sitemesh/parser/AbstractPage.java | 27 ------------------- .../com/opensymphony/sitemesh/Content.java | 6 ----- .../compatability/Content2HTMLPage.java | 4 --- .../compatability/HTMLPage2Content.java | 4 --- .../webapp/decorator/NoDecorator.java | 2 -- .../sitemesh/parser/HTMLPageParserTest.java | 1 - 10 files changed, 5 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index b3da84d2..5a591002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build +target out *.iml *.ipr *.iws +.idea dist diff --git a/build.properties b/build.properties index fe3ce13b..bb789577 100644 --- a/build.properties +++ b/build.properties @@ -18,4 +18,4 @@ compile.nowarn = off Name = OpenSymphony SiteMesh name = sitemesh -version = 2.5-atlassian-2 +version = 2.5-atlassian-3 diff --git a/pom.xml b/pom.xml index f0c3ee56..5bf59892 100644 --- a/pom.xml +++ b/pom.xml @@ -4,6 +4,7 @@ 4.0.0 opensymphony sitemesh - 2.5-atlassian-4 + 2.5-atlassian-5 jar SiteMesh