Author: johnh
Date: Thu Sep  4 17:35:36 2008
New Revision: 692314

URL: http://svn.apache.org/viewvc?rev=692314&view=rev
Log:
Introducing class MutableHtmlContent and subclassing it with Gadget. This 
accommodates part 1 of SHINDIG-571.

MutableHtmlContent manages a String and its equivalent parse tree 
representation, ensuring with certain guarantees, as documented, that the two 
remain in sync. In particular, a parse tree derived from String in 
MutableHtmlContent cannot me modified once the source String from which it was 
created is changed.

This change precedes the use of MutableHtmlContent in HttpResponse, enabling 
consolidation of rewriting logic for both ContentRewriter APIs: Gadget-based 
and HttpResponse-based.


Added:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MutableHtmlContent.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/MutableHtmlContentTest.java
Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java?rev=692314&r1=692313&r2=692314&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
 Thu Sep  4 17:35:36 2008
@@ -19,9 +19,7 @@
 
 import org.apache.shindig.common.ContainerConfig;
 import org.apache.shindig.gadgets.http.HttpResponse;
-import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
 import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
-import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
 import org.apache.shindig.gadgets.spec.Preload;
@@ -30,11 +28,8 @@
 import org.json.JSONArray;
 import org.json.JSONException;
 
-import java.io.IOException;
-import java.io.StringWriter;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Future;
 
@@ -42,7 +37,7 @@
  * Intermediary representation of all state associated with processing
  * of a single gadget request.
  */
-public class Gadget {
+public class Gadget extends MutableHtmlContent {
   private final GadgetContext context;
   
   /**
@@ -97,7 +92,7 @@
    * @return The (immutable) View applicable for the current request (part of 
GadgetSpec).
    */
   public View getCurrentView() {
-       return currentView;
+    return currentView;
   }
 
   /**
@@ -132,146 +127,20 @@
     }
     return view;
   }
-  
-  private String content;
-  private int contentParseId;
-  
-  /**
-   * Retrieves the current content for this gadget in String form.
-   * If gadget content has been retrieved in parse tree form and has
-   * been edited, the String form is computed from the parse tree by
-   * rendering it. It is <b>strongly</b> encouraged to avoid switching
-   * between retrieval of parse tree (through [EMAIL PROTECTED] getParseTree}),
-   * with subsequent edits and retrieval of String contents to avoid
-   * repeated serialization and deserialization.
-   * @return Renderable/active content for the gadget.
-   */
-  public String getContent() {
-       if (parseEditId > contentParseId) {
-         // Regenerate content from parse tree node, since the parse tree
-         // was modified relative to the last time content was generated from 
it.
-         // This is an expensive operation that should happen only once
-         // per rendering cycle: all rewriters (or other manipulators)
-         // operating on the parse tree should happen together.
-         contentParseId = parseEditId;
-      StringWriter sw = new StringWriter();
-      for (GadgetHtmlNode node : parseTree.getChildren()) {
-       try {
-         node.render(sw);
-       } catch (IOException e) {
-          // Never happens.
-       }
-      }
-      content = sw.toString();
-       }
-    return content;
-  }
-  
-  /**
-   * Sets the content for the gadget as a raw String. Note, this operation
-   * may be done at any time, even after a parse tree node has been retrieved
-   * and modified (though a warning will be emitted in this case). Once
-   * new content has been set, all subsequent edits to parse trees generated
-   * from the <i>previous</i> content will be invalid, throwing an
-   * [EMAIL PROTECTED] IllegalStateException}.
-   * @param newContent New content for the gadget.
-   */
-  public void setContent(String newContent) {
-       if (!content.equals(newContent)) {
-      content = newContent;
-         if (editListener != null) {
-           editListener.stringEdited();
-         }
-       }
-  }
-  
-  private GadgetHtmlNode parseTree;
-  public static final String ROOT_NODE_TAG_NAME = "gadget-root";
-  
-  /**
-   * Retrieves the contents of the gadget in parse tree form, if a
-   * [EMAIL PROTECTED] GadgetHtmlParser} is configured and is able to parse 
the string
-   * contents appropriately. The resultant parse tree has a special,
-   * single top-level node that wraps all subsequent content, with
-   * tag name [EMAIL PROTECTED] ROOT_NODE_TAG_NAME}. While it may be edited 
just
-   * as any other node may, doing so is pointless since the root node
-   * is stripped out during rendering. Any edits to the returned parse
-   * tree performed after the source [EMAIL PROTECTED] Gadget} has new content
-   * set via [EMAIL PROTECTED] setContent} will throw an [EMAIL PROTECTED] 
IllegalStateException}
-   * to maintain content consistency in the gadget. To modify a gadget's
-   * contents by parse tree after setting new String contents,
-   * this method must be called again. However, this practice is highly
-   * discouraged, as parsing a tree from String is a costly operation.
-   * @return Top-level node whose children represent the gadget's contents, or
-   *         null if no parser is configured or if String contents are null.
-   * @throws GadgetException Throw by the GadgetHtmlParser generating the tree 
from String.
-   */
-  public GadgetHtmlNode getParseTree() throws GadgetException {
-       if (parseTree != null && !editListener.stringWasEdited()) {
-         return parseTree;
-       }
-       
-       if (content == null || contentParser == null) {
-         return null;
-       }
-       
-       // One ContentEditListener per parse tree.
-       editListener = new ContentEditListener();
-       parseTree = new GadgetHtmlNode(ROOT_NODE_TAG_NAME, null);
-       List<ParsedHtmlNode> parsed = contentParser.parse(content);
-       for (ParsedHtmlNode parsedNode : parsed) {
-         parseTree.appendChild(new GadgetHtmlNode(parsedNode, editListener));
-       }
-       
-       // Parse tree created from content: edit IDs are the same
-       contentParseId = parseEditId;
-       return parseTree;
-  }
-
-  private ContainerConfig containerConfig;
-  private GadgetHtmlParser contentParser;
-  private int parseEditId;
-  private ContentEditListener editListener;
-  
   public Gadget(GadgetContext context, GadgetSpec spec,
       Collection<JsLibrary> jsLibraries, ContainerConfig containerConfig,
       GadgetHtmlParser contentParser) {
+    super(contentParser);
+    
     this.context = context;
     this.spec = spec;
     this.jsLibraries = jsLibraries;
-    this.containerConfig = containerConfig;
-    this.contentParser = contentParser;
-    this.currentView = getView(this.containerConfig);
+    this.currentView = getView(containerConfig);
     if (this.currentView != null) {
       // View might be invalid or associated with no content (type=URL)
-      this.content = this.currentView.getContent();
+      setContent(this.currentView.getContent());
     } else {
-      this.content = null;
+      setContent(null);
     }
-    contentParseId = 0;
-  }
-  
-  // Intermediary object tracking edit behavior for the Gadget to help maintain
-  // state consistency. GadgetHtmlNode calls nodeEdited whenever a modification
-  // is made to its original source.
-  private class ContentEditListener implements GadgetHtmlNode.EditListener {
-       private boolean stringEdited = false;
-       
-       public void nodeEdited() {
-         ++parseEditId;
-         if (stringEdited) {
-               // Parse tree is invalid: a new String representation was set
-               // as tree source in the meantime.
-               throw new IllegalStateException("Edited parse node after 
setting String content");
-         }
-       }
-       
-       private void stringEdited() {
-         stringEdited = true;
-       }
-       
-       private boolean stringWasEdited() {
-         return stringEdited;
-       }
   }
 }
\ No newline at end of file

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MutableHtmlContent.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MutableHtmlContent.java?rev=692314&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MutableHtmlContent.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MutableHtmlContent.java
 Thu Sep  4 17:35:36 2008
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.gadgets;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+
+import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+
+/**
+ * Object that maintains a String representation of arbitrary contents
+ * and a consistent view of those contents as an HTML parse tree.
+ */
+public class MutableHtmlContent {
+  private String content;
+  private GadgetHtmlNode parseTree;
+  private ContentEditListener editListener;
+  private int parseEditId;
+  private int contentParseId;
+  private final GadgetHtmlParser contentParser;
+
+  public MutableHtmlContent(GadgetHtmlParser contentParser) {
+    this.contentParser = contentParser;
+    this.contentParseId = parseEditId = 0;
+  }
+
+  public static final String ROOT_NODE_TAG_NAME = "gadget-root";
+  
+  /**
+   * Retrieves the current content for this object in String form.
+   * If content has been retrieved in parse tree form and has
+   * been edited, the String form is computed from the parse tree by
+   * rendering it. It is <b>strongly</b> encouraged to avoid switching
+   * between retrieval of parse tree (through [EMAIL PROTECTED] getParseTree}),
+   * with subsequent edits and retrieval of String contents to avoid
+   * repeated serialization and deserialization.
+   * @return Renderable/active content.
+   */
+  public String getContent() {
+    if (parseEditId > contentParseId) {
+      // Regenerate content from parse tree node, since the parse tree
+      // was modified relative to the last time content was generated from it.
+      // This is an expensive operation that should happen only once
+      // per rendering cycle: all rewriters (or other manipulators)
+      // operating on the parse tree should happen together.
+      contentParseId = parseEditId;
+      StringWriter sw = new StringWriter();
+      for (GadgetHtmlNode node : parseTree.getChildren()) {
+        try {
+          node.render(sw);
+        } catch (IOException e) {
+          // Never happens.
+        }
+      }
+      content = sw.toString();
+    }
+    return content;
+  }
+  
+  /**
+   * Sets the object's content as a raw String. Note, this operation
+   * may be done at any time, even after a parse tree node has been retrieved
+   * and modified (though a warning will be emitted in this case). Once
+   * new content has been set, all subsequent edits to parse trees generated
+   * from the <i>previous</i> content will be invalid, throwing an
+   * [EMAIL PROTECTED] IllegalStateException}.
+   * @param newContent New content.
+   */
+  public void setContent(String newContent) {
+    if (content == null ||
+        !content.equals(newContent)) {
+      content = newContent;
+      if (editListener != null) {
+        editListener.stringEdited();
+      }
+    }
+  }
+  
+  /**
+   * Retrieves the object contents in parse tree form, if a
+   * [EMAIL PROTECTED] GadgetHtmlParser} is configured and is able to parse 
the string
+   * contents appropriately. The resultant parse tree has a special,
+   * single top-level node that wraps all subsequent content, with
+   * tag name [EMAIL PROTECTED] ROOT_NODE_TAG_NAME}. While it may be edited 
just
+   * as any other node may, doing so is pointless since the root node
+   * is stripped out during rendering. Any edits to the returned parse
+   * tree performed after the source [EMAIL PROTECTED] MutableHtmlContent} has 
new content
+   * set via [EMAIL PROTECTED] setContent} will throw an [EMAIL PROTECTED] 
IllegalStateException}
+   * to maintain content consistency in the object. To modify the object's
+   * contents by parse tree after setting new String contents,
+   * this method must be called again. However, this practice is highly
+   * discouraged, as parsing a tree from String is a costly operation.
+   * @return Top-level node whose children represent the gadget's contents, or
+   *         null if no parser is configured, String contents are null, or 
contents unparseable.
+   */
+  public GadgetHtmlNode getParseTree() {
+    if (parseTree != null && !editListener.stringWasEdited()) {
+      return parseTree;
+    }
+  
+    if (content == null || contentParser == null) {
+      return null;
+    }
+  
+    // One ContentEditListener per parse tree.
+    editListener = new ContentEditListener();
+    parseTree = new GadgetHtmlNode(ROOT_NODE_TAG_NAME, null);
+    List<ParsedHtmlNode> parsed = null;
+    try {
+      parsed = contentParser.parse(content);
+    } catch (GadgetException e) {
+      // TODO: emit info message
+      return null;
+    }
+  
+    if (parsed == null) {
+      return null;
+    }
+    
+    for (ParsedHtmlNode parsedNode : parsed) {
+      parseTree.appendChild(new GadgetHtmlNode(parsedNode, editListener));
+    }
+  
+    // Parse tree created from content: edit IDs are the same
+    contentParseId = parseEditId;
+    return parseTree;
+  }
+  
+  // Intermediary object tracking edit behavior for the MutableHtmlContent to 
help maintain
+  // state consistency. GadgetHtmlNode calls nodeEdited whenever a modification
+  // is made to its original source.
+  private class ContentEditListener implements GadgetHtmlNode.EditListener {
+    private boolean stringEdited = false;
+  
+    public void nodeEdited() {
+      ++parseEditId;
+      if (stringEdited) {
+        // Parse tree is invalid: a new String representation was set
+        // as tree source in the meantime.
+        throw new IllegalStateException("Edited parse node after setting 
String content");
+      }
+    }
+  
+    private void stringEdited() {
+      stringEdited = true;
+    }
+  
+    private boolean stringWasEdited() {
+      return stringEdited;
+    }
+  }
+}

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java?rev=692314&r1=692313&r2=692314&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
 Thu Sep  4 17:35:36 2008
@@ -21,14 +21,9 @@
 import static org.easymock.EasyMock.expect;
 import static org.easymock.classextension.EasyMock.replay;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import org.apache.shindig.common.ContainerConfig;
 import org.apache.shindig.gadgets.http.HttpResponse;
-import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
 import org.apache.shindig.gadgets.parse.caja.CajaHtmlParser;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
@@ -101,80 +96,6 @@
 
     assertEquals("DEFAULT VIEW", view.getContent());
   }
-  
-  @Test
-  public void getContentAndParseTreeNoSets() throws Exception {
-       replay(config);
-
-       String content = gadget.getContent();
-       assertEquals("DEFAULT VIEW", content);
-       
-       GadgetHtmlNode root = gadget.getParseTree();
-       assertEquals(1, root.getChildren().size());
-       assertTrue(root.getChildren().get(0).isText());
-       assertEquals(content, root.getChildren().get(0).getText());
-       
-       assertSame(content, gadget.getContent());
-       assertSame(root, gadget.getParseTree());
-  }
-  
-  @Test
-  public void modifyContentReflectedInTree() throws Exception {
-       replay(config);
-       
-       gadget.setContent("NEW CONTENT");
-       GadgetHtmlNode root = gadget.getParseTree();
-       assertEquals(1, root.getChildren().size());
-       assertEquals("NEW CONTENT", root.getChildren().get(0).getText());
-  }
-  
-  @Test
-  public void modifyTreeReflectedInContent() throws Exception {
-       replay(config);
-       
-       GadgetHtmlNode root = gadget.getParseTree();
-       
-       // First child should be text node per other tests. Modify it.
-       root.getChildren().get(0).setText("FOO CONTENT");
-       assertEquals("FOO CONTENT", gadget.getContent());
-       
-       // Do it again
-       root.getChildren().get(0).setText("BAR CONTENT");
-       assertEquals("BAR CONTENT", gadget.getContent());
-       
-       // GadgetHtmlNode hasn't changed because string hasn't changed
-       assertSame(root, gadget.getParseTree());
-  }
-  
-  @Test
-  public void staleTreeEditsInvalidatedAfterContentSet() throws Exception {
-       replay(config);
-       
-       GadgetHtmlNode firstRoot = gadget.getParseTree();
-       
-       // Re-set content
-       gadget.setContent("INVALIDATING CONTENT");
-       
-       // Should still be able to obtain this.
-       GadgetHtmlNode secondRoot = gadget.getParseTree();
-       assertNotSame(firstRoot, secondRoot);
-       
-       // Should be able to *obtain* first child node...
-       GadgetHtmlNode firstTextNode = firstRoot.getChildren().get(0);
-       try {
-      // ...but not edit it.
-      firstTextNode.setText("STALE-SET CONTENT");
-      fail("Should not be able to modify stale parse tree");
-       } catch (IllegalStateException e) {
-         // Expected condition.
-       }
-       
-       assertEquals("INVALIDATING CONTENT", 
secondRoot.getChildren().get(0).getText());
-       
-       // For good measure, modify secondRoot and get content
-       secondRoot.getChildren().get(0).setText("NEW CONTENT");
-       assertEquals("NEW CONTENT", gadget.getContent());
-  }
 
   @Test
   public void getAliasedView() {

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/MutableHtmlContentTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/MutableHtmlContentTest.java?rev=692314&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/MutableHtmlContentTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/MutableHtmlContentTest.java
 Thu Sep  4 17:35:36 2008
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
+import org.apache.shindig.gadgets.parse.caja.CajaHtmlParser;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class MutableHtmlContentTest {
+  private MutableHtmlContent mhc;
+  
+  @Before
+  public void setUp() throws Exception {
+    // Note dependency on CajaHtmlParser - this isn't particularly ideal but is
+    // sufficient given that this test doesn't exercise the parser extensively 
at all,
+    // instead focusing on the additional utility provided by 
MutableHtmlContent
+    mhc = new MutableHtmlContent(new CajaHtmlParser());
+    mhc.setContent("DEFAULT VIEW");
+  }
+  
+  @Test
+  public void getContentAndParseTreeNoSets() throws Exception {
+    String content = mhc.getContent();
+    assertEquals("DEFAULT VIEW", content);
+  
+    GadgetHtmlNode root = mhc.getParseTree();
+    assertEquals(1, root.getChildren().size());
+    assertTrue(root.getChildren().get(0).isText());
+    assertEquals(content, root.getChildren().get(0).getText());
+  
+    assertSame(content, mhc.getContent());
+    assertSame(root, mhc.getParseTree());
+  }
+  
+  @Test
+  public void modifyContentReflectedInTree() throws Exception {
+    mhc.setContent("NEW CONTENT");
+    GadgetHtmlNode root = mhc.getParseTree();
+    assertEquals(1, root.getChildren().size());
+    assertEquals("NEW CONTENT", root.getChildren().get(0).getText());
+  }
+  
+  @Test
+  public void modifyTreeReflectedInContent() throws Exception {
+    GadgetHtmlNode root = mhc.getParseTree();
+  
+    // First child should be text node per other tests. Modify it.
+    root.getChildren().get(0).setText("FOO CONTENT");
+    assertEquals("FOO CONTENT", mhc.getContent());
+  
+    // Do it again
+    root.getChildren().get(0).setText("BAR CONTENT");
+    assertEquals("BAR CONTENT", mhc.getContent());
+  
+    // GadgetHtmlNode hasn't changed because string hasn't changed
+    assertSame(root, mhc.getParseTree());
+  }
+  
+  @Test
+  public void staleTreeEditsInvalidatedAfterContentSet() throws Exception {
+    GadgetHtmlNode firstRoot = mhc.getParseTree();
+  
+    // Re-set content
+    mhc.setContent("INVALIDATING CONTENT");
+  
+    // Should still be able to obtain this.
+    GadgetHtmlNode secondRoot = mhc.getParseTree();
+    assertNotSame(firstRoot, secondRoot);
+  
+    // Should be able to *obtain* first child node...
+    GadgetHtmlNode firstTextNode = firstRoot.getChildren().get(0);
+    try {
+      // ...but not edit it.
+      firstTextNode.setText("STALE-SET CONTENT");
+      fail("Should not be able to modify stale parse tree");
+    } catch (IllegalStateException e) {
+      // Expected condition.
+    }
+  
+    assertEquals("INVALIDATING CONTENT", 
secondRoot.getChildren().get(0).getText());
+  
+    // For good measure, modify secondRoot and get content
+    secondRoot.getChildren().get(0).setText("NEW CONTENT");
+    assertEquals("NEW CONTENT", mhc.getContent());
+  }
+}


Reply via email to