From 34e34dd51331a1a4166a620f14c2214b23aaab0b Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Tue, 21 Nov 2023 18:01:17 +0000 Subject: [PATCH] [optimise] Namespace and indentation performance fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are strictly incorrect, as they don’t do the XMLWriter substitution of special characters, BUT we could do this ONCE for every string; it does demonstrate that `XMLWriter.writeChars()` is a big part of the problem. --- .../util/serializer/IndentingXMLWriter.java | 37 +++++++++++-------- .../org/exist/util/serializer/XMLWriter.java | 24 +++++++----- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/exist-core/src/main/java/org/exist/util/serializer/IndentingXMLWriter.java b/exist-core/src/main/java/org/exist/util/serializer/IndentingXMLWriter.java index 12a09abe46e..85886433f18 100644 --- a/exist-core/src/main/java/org/exist/util/serializer/IndentingXMLWriter.java +++ b/exist-core/src/main/java/org/exist/util/serializer/IndentingXMLWriter.java @@ -21,28 +21,28 @@ */ package org.exist.util.serializer; -import java.io.IOException; -import java.io.Writer; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Properties; - -import javax.xml.transform.OutputKeys; -import javax.xml.transform.TransformerException; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.Namespaces; import org.exist.dom.QName; import org.exist.storage.serializers.EXistOutputKeys; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.TransformerException; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.Properties; + public class IndentingXMLWriter extends XMLWriter { private final static Logger LOG = LogManager.getLogger(IndentingXMLWriter.class); private boolean indent = false; private int indentAmount = 4; - private String indentChars = " "; + private char[] indentChars = new char[0]; private int level = 0; private boolean afterTag = false; private boolean sameline = false; @@ -224,12 +224,19 @@ protected void indent() throws TransformerException { if (!indent || whitespacePreserve) { return; } - final int spaces = indentAmount * level; - while (spaces >= indentChars.length()) { - indentChars += indentChars; + final int padding = indentAmount * level + 1; /* 1 for leading \n */ + if (indentChars.length < padding) { + indentChars = new char[padding + padding]; + Arrays.fill(indentChars, ' '); + indentChars[0] = '\n'; + } + try { + // aim is to use a single Writer.write to avoid efficiency overheads + // char[] is the most efficient form (least copies) + writer.write(indentChars, 0, padding); + } catch(final IOException ioe) { + throw new TransformerException(ioe.getMessage(), ioe); } - super.characters("\n"); - super.characters(indentChars.subSequence(0, spaces)); sameline = false; } diff --git a/exist-core/src/main/java/org/exist/util/serializer/XMLWriter.java b/exist-core/src/main/java/org/exist/util/serializer/XMLWriter.java index abf92f96c9c..1c97047e4ba 100644 --- a/exist-core/src/main/java/org/exist/util/serializer/XMLWriter.java +++ b/exist-core/src/main/java/org/exist/util/serializer/XMLWriter.java @@ -44,6 +44,9 @@ public class XMLWriter implements SerializerWriter { private final static IllegalStateException EX_CHARSET_NULL = new IllegalStateException("Charset should never be null!"); + + private static final char[] NAMESPACE_FN_XMLNS_PREFIX = " xmlns:".toCharArray(); + private static final char[] NAMESPACE_FN_XMLNS_NO_PREFIX = " xmlns=\"".toCharArray(); protected final static Properties defaultProperties = new Properties(); static { @@ -292,21 +295,22 @@ public void namespace(final String prefix, final String nsURI) throws Transforme } if(prefix != null && !prefix.isEmpty()) { - writer.write(' '); - writer.write("xmlns"); - writer.write(':'); + writer.write(NAMESPACE_FN_XMLNS_PREFIX); writer.write(prefix); writer.write("=\""); - writeChars(nsURI, true); + //TODO (AP) - test, just write as a string + //writeChars(nsURI, true); + writer.write(nsURI); writer.write('"'); } else { if(defaultNamespace.equals(nsURI)) { return; } - writer.write(' '); - writer.write("xmlns"); + writer.write(NAMESPACE_FN_XMLNS_NO_PREFIX); writer.write("=\""); - writeChars(nsURI, true); + //TODO (AP) - test, just write as a string + //writeChars(nsURI, true); + writer.write(nsURI); writer.write('"'); defaultNamespace= nsURI; } @@ -348,7 +352,9 @@ public void attribute(final QName qname, final CharSequence value) throws Transf } writer.write(qname.getLocalPart()); writer.write("=\""); - writeChars(value, true); + //TODO (AP) - perf hack - needs to respect specials (e.g. precompute) + //writeChars(value, true); + writer.append(value); writer.write('"'); } catch(final IOException ioe) { throw new TransformerException(ioe.getMessage(), ioe); @@ -364,7 +370,7 @@ public void characters(final CharSequence chars) throws TransformerException { if(tagIsOpen) { closeStartTag(false); } - writeChars(chars, false); + writer.append(chars); } catch(final IOException ioe) { throw new TransformerException(ioe.getMessage(), ioe); }