Correction, bad description: Gadget subclasses MutableHtmlContent, not the
other way around.

On Thu, Sep 4, 2008 at 5:35 PM, <[EMAIL PROTECTED]> wrote:

> 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