From a148b0dc4e10dbf530cf734c0808d0fb4edd6719 Mon Sep 17 00:00:00 2001
From: David Winslow <cdwinslow@gmail.com>
Date: Tue, 1 May 2012 14:44:55 -0400
Subject: [PATCH] Mark and reset for TranslatorSupport

---
 .../geotools/xml/transform/TransformerBase.java    |  195 +++++++++++++++++++-
 1 files changed, 188 insertions(+), 7 deletions(-)

diff --git a/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java b/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java
index 10a09f8..fad5193 100644
--- a/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java
+++ b/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java
@@ -18,8 +18,10 @@ package org.geotools.xml.transform;
 
 import java.io.StringWriter;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -434,6 +436,94 @@ public abstract class TransformerBase {
         protected final Attributes NULL_ATTS = new AttributesImpl();
         protected NamespaceSupport nsSupport = new NamespaceSupport();
         protected SchemaLocationSupport schemaLocation;
+
+        /**
+         * Is a mark currently set?
+         *
+         * @see #mark()
+         */
+        private boolean marked;
+
+        /**
+         * The queue of write operations pending for this translator.
+         * This should be empty if no mark is set.
+         */
+        private List<Action> pending = new ArrayList<Action>();
+
+        /**
+         * An Action records a call to one of the SAX-event-generating methods
+         * on this translator.
+         */
+        private interface Action {
+            void commit();
+        }
+
+        /**
+         * The Start class implements an Action corresponding to starting an
+         * XML element
+         */
+        private class Start implements Action {
+            private final String element;
+            private final Attributes attributes;
+
+            public Start(String element, Attributes attributes) {
+                this.element = element;
+                this.attributes = new AttributesImpl(attributes);
+            }
+
+            public void commit() {
+                _start(element, attributes);
+            }
+        }
+
+        /**
+         * The Chars class implements an Action corresponding to writing a text
+         * block in the XML output
+         */
+        private class Chars implements Action {
+            private final String text;
+
+            public Chars(String text) {
+                this.text = text;
+            }
+
+            public void commit() {
+                _chars(text);
+            }
+        }
+
+        /**
+         * The CData class implements an Action corresponding to writing a
+         * CDATA block in the XML output
+         */
+        private class CData implements Action {
+            private final String text;
+
+            public CData(String text) {
+                this.text = text;
+            }
+
+            public void commit() {
+                _cdata(text);
+            }
+        }
+
+        /**
+         * The End class implements an Action corresponding to closing an XML
+         * element
+         */
+        private class End implements Action {
+            private final String element;
+
+            public End(String element) {
+                this.element = element;
+            }
+
+            public void commit() {
+                _end(element);
+            }
+        }
+
         /**
          * Subclasses should check this flag in case an abort message was sent
          * and stop any internal iteration if false.
@@ -454,11 +544,69 @@ public abstract class TransformerBase {
             this(contentHandler, prefix, nsURI);
             this.schemaLocation = schemaLocation;
         }
-        
+
         public void abort() {
             running = false;
         }
-        
+
+        /**
+         * Set a mark() to which we can later "roll back" writes.  After a call
+         * to mark(), the Translator stores pending write operations in memory
+         * until commit() is called.  The pending writes can be discarded with
+         * the reset() method.
+         *
+         * Typically, one would use marks in conjunction with an exception handler:
+         *
+         * <pre>
+         *   void encodeFoo(Foo f) {
+         *     try {
+         *       mark();
+         *       element(foo.riskyMethod());
+         *       element(foo.dangerousMethod());
+         *       commit();
+         *     } catch (BadThingHappened disaster) {
+         *         mitigate(disaster);
+         *         reset();
+         *     }
+         *   }
+         * </pre>
+         *
+         * @throws IllegalStateException if a mark is already set
+         */
+        protected void mark() {
+            if (marked) throw new IllegalStateException("Mark already set!");
+            marked = true;
+        }
+
+        /**
+         * Discard pending write operations after a mark() has been set.
+         *
+         * This method is safe to call even if no mark is set - so it returns
+         * to a "known good" state as far as marks are concerned.
+         *
+         * @see #mark()
+         */
+        protected void reset() {
+            pending.clear();
+            marked = false;
+        }
+
+        /**
+         * Commit pending write operations.  After setting a mark, this method
+         * will commit the pending writes.
+         *
+         * @see #mark()
+         * @throws IllegalStateException if no mark is set
+         */
+        protected void commit() {
+            if (!marked) throw new IllegalStateException("Can't commit without a mark!");
+            for (Action a : pending) {
+                a.commit();
+            }
+            pending.clear();
+            marked = false;
+        }
+
         /**
          * Utility method to copy namespace declarations from "sub" translators
          * into this ns support...
@@ -498,7 +646,7 @@ public abstract class TransformerBase {
         protected void element(String element, String content) {
             element(element, content, NULL_ATTS);
         }
-        
+
         /**
          * Will only issue the provided element if content is non empty
          * @param element
@@ -525,6 +673,14 @@ public abstract class TransformerBase {
         }
 
         protected void start(String element, Attributes atts) {
+            if (marked) {
+                pending.add(new Start(element, atts));
+            } else {
+                _start(element, atts);
+            }
+        }
+
+        private void _start(String element, Attributes atts) {
             try {
                 String el = (prefix == null) ? element : (prefix + ":"
                     + element);
@@ -535,6 +691,14 @@ public abstract class TransformerBase {
         }
 
         protected void chars(String text) {
+            if (marked) {
+                pending.add(new Chars(text));
+            } else {
+                _chars(text);
+            }
+        }
+
+        private void _chars(String text) {
             try {
                 char[] ch = text.toCharArray();
                 contentHandler.characters(ch, 0, ch.length);
@@ -544,6 +708,14 @@ public abstract class TransformerBase {
         }
 
         protected void end(String element) {
+            if (marked) {
+                pending.add(new End(element));
+            } else {
+                _end(element);
+            }
+        }
+
+        private void _end(String element) {
             try {
                 String el = (prefix == null) ? element : (prefix + ":"
                     + element);
@@ -553,13 +725,22 @@ public abstract class TransformerBase {
             }
         }
 
-        protected void cdata( String cdata ) {
-            if ( contentHandler instanceof LexicalHandler ) {
+        protected void cdata(String cdata) {
+            if (marked) {
+                pending.add(new CData(cdata));
+            } else {
+                _cdata(cdata);
+            }
+        }
+
+        private void _cdata(String cdata) {
+            if (contentHandler instanceof LexicalHandler) {
                 LexicalHandler lexicalHandler = (LexicalHandler) contentHandler;
                 try {
                     lexicalHandler.startCDATA();
-                    chars(cdata);
-                    lexicalHandler.endCDATA();    
+                    char[] carray = cdata.toCharArray();
+                    contentHandler.characters(carray, 0, carray.length);
+                    lexicalHandler.endCDATA();
                 }
                 catch( SAXException e ) {
                     throw new RuntimeException( e );
-- 
1.7.7.6

