Merge "Added structuralChange flag to ContentChangeEvent"
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/DocumentEventTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/DocumentEventTest.java
new file mode 100644
index 0000000..4e936b6
--- /dev/null
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/DocumentEventTest.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Carsten Hiesserich and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ * 		Carsten Hiesserich  - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.vex.core.internal.dom;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.vex.core.internal.io.XMLFragment;
+import org.eclipse.vex.core.provisional.dom.AttributeChangeEvent;
+import org.eclipse.vex.core.provisional.dom.ContentChangeEvent;
+import org.eclipse.vex.core.provisional.dom.ContentRange;
+import org.eclipse.vex.core.provisional.dom.IDocument;
+import org.eclipse.vex.core.provisional.dom.IDocumentFragment;
+import org.eclipse.vex.core.provisional.dom.IDocumentListener;
+import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.NamespaceDeclarationChangeEvent;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DocumentEventTest {
+
+	private IDocument document;
+	private IElement childNode;
+	private ContentChangeEvent contentChangeEvent = null;
+
+	@Before
+	public void setUp() throws Exception {
+		document = new Document(new QualifiedName(null, "root"));
+		childNode = document.insertElement(2, new QualifiedName(null, "child"));
+		document.addDocumentListener(new IDocumentListener() {
+
+			public void attributeChanged(final AttributeChangeEvent event) {
+
+			}
+
+			public void namespaceChanged(final NamespaceDeclarationChangeEvent event) {
+
+			}
+
+			public void beforeContentDeleted(final ContentChangeEvent event) {
+
+			}
+
+			public void beforeContentInserted(final ContentChangeEvent event) {
+
+			}
+
+			public void contentDeleted(final ContentChangeEvent event) {
+				contentChangeEvent = event;
+			}
+
+			public void contentInserted(final ContentChangeEvent event) {
+				contentChangeEvent = event;
+			}
+
+		});
+	}
+
+	@Test
+	public void testInsertText() throws Exception {
+		document.insertText(childNode.getEndOffset(), "Hello World");
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertFalse("Expecting no structural change", contentChangeEvent.isStructuralChange());
+	}
+
+	@Test
+	public void testInsertElement() throws Exception {
+		document.insertElement(childNode.getEndOffset(), new QualifiedName(null, "subchild"));
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertTrue("Expecting structural change", contentChangeEvent.isStructuralChange());
+		assertEquals(childNode, contentChangeEvent.getParent());
+	}
+
+	@Test
+	public void testDeleteText() throws Exception {
+		document.insertText(childNode.getEndOffset(), "Hello World");
+		contentChangeEvent = null;
+		document.delete(new ContentRange(childNode.getRange().getStartOffset() + 1, childNode.getRange().getStartOffset() + 6));
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertFalse("Expecting no structural change", contentChangeEvent.isStructuralChange());
+		assertEquals(childNode, contentChangeEvent.getParent());
+	}
+
+	@Test
+	public void testDeleteNodeWithText() throws Exception {
+		document.insertText(childNode.getEndOffset(), "Hello World");
+		contentChangeEvent = null;
+		document.delete(childNode.getRange());
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertTrue("Expecting structural change", contentChangeEvent.isStructuralChange());
+		assertEquals(document.getRootElement(), contentChangeEvent.getParent());
+	}
+
+	@Test
+	public void testInsertTextFragment() throws Exception {
+		final IDocumentFragment fragment = new XMLFragment("Hello World").getDocumentFragment();
+		document.insertFragment(childNode.getStartOffset() + 1, fragment);
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertFalse("Expecting no structural change", contentChangeEvent.isStructuralChange());
+		assertEquals(childNode, contentChangeEvent.getParent());
+	}
+
+	@Test
+	public void testInsertFragmentWithNode() throws Exception {
+		final IDocumentFragment fragment = new XMLFragment("<child>Hello World</child>").getDocumentFragment();
+		document.insertFragment(childNode.getStartOffset() + 1, fragment);
+		assertNotNull("Expecting ContentChangeEvent", contentChangeEvent);
+		assertTrue("Expecting structural change", contentChangeEvent.isStructuralChange());
+		assertEquals(childNode, contentChangeEvent.getParent());
+	}
+
+}
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/tests/VEXCoreTestSuite.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/tests/VEXCoreTestSuite.java
index 360a8f5..dcf714b 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/tests/VEXCoreTestSuite.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/tests/VEXCoreTestSuite.java
@@ -28,6 +28,7 @@
 import org.eclipse.vex.core.internal.dom.CopyVisitorTest;
 import org.eclipse.vex.core.internal.dom.DTDValidatorTest;
 import org.eclipse.vex.core.internal.dom.DeepCopyTest;
+import org.eclipse.vex.core.internal.dom.DocumentEventTest;
 import org.eclipse.vex.core.internal.dom.DocumentFragmentTest;
 import org.eclipse.vex.core.internal.dom.DocumentTest;
 import org.eclipse.vex.core.internal.dom.GapContentTest;
@@ -69,7 +70,7 @@
 		DeepCopyTest.class, PropertyTest.class, RuleTest.class, BlockElementBoxTest.class, ImageBoxTest.class, DocumentWriterTest.class, DTDValidatorTest.class, GapContentTest.class,
 		SpaceNormalizerTest.class, TextWrapperTest.class, TestBlockElementBox.class, TestBlocksInInlines.class, TestDocumentTextBox.class, TestStaticTextBox.class, TableLayoutTest.class,
 		LayoutTestSuite.class, ListenerListTest.class, DocumentFragmentTransferTest.class, XMLFragmentTest.class, VexWidgetTest.class, L2SimpleEditingTest.class, L2SelectionTest.class,
-		L2CommentEditingTest.class, L2XmlInsertionTest.class
+		L2CommentEditingTest.class, L2XmlInsertionTest.class, DocumentEventTest.class
 
 })
 public class VEXCoreTestSuite {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
index 04593e1..cacf6d4 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
@@ -10,6 +10,7 @@
  *     Igor Jacy Lino Campista - Java 5 warnings fixed (bug 311325)
  *     Florian Thienel - refactoring to full fledged DOM
  *     Carsten Hiesserich - bug fixes (bug 407801, 410659)
+ *     Carsten Hiesserich - added structuralChange flag to ContentChangeEvent
  *******************************************************************************/
 package org.eclipse.vex.core.internal.dom;
 
@@ -257,21 +258,21 @@
 					throw new DocumentValidationException(MessageFormat.format("Cannot insert text ''{0}'' at offset {1}.", text, offset));
 				}
 
-				fireBeforeContentInserted(new ContentChangeEvent(Document.this, element, new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireBeforeContentInserted(new ContentChangeEvent(Document.this, element, new ContentRange(offset, offset + adjustedText.length() - 1), false));
 				getContent().insertText(offset, adjustedText);
-				fireContentInserted(new ContentChangeEvent(Document.this, element, new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireContentInserted(new ContentChangeEvent(Document.this, element, new ContentRange(offset, offset + adjustedText.length() - 1), false));
 			}
 
 			public void visit(final IText text) {
-				fireBeforeContentInserted(new ContentChangeEvent(Document.this, text.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireBeforeContentInserted(new ContentChangeEvent(Document.this, text.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1), false));
 				getContent().insertText(offset, adjustedText);
-				fireContentInserted(new ContentChangeEvent(Document.this, text.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireContentInserted(new ContentChangeEvent(Document.this, text.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1), false));
 			}
 
 			public void visit(final IComment comment) {
-				fireBeforeContentInserted(new ContentChangeEvent(Document.this, comment.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireBeforeContentInserted(new ContentChangeEvent(Document.this, comment.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1), false));
 				getContent().insertText(offset, adjustedText);
-				fireContentInserted(new ContentChangeEvent(Document.this, comment.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1)));
+				fireContentInserted(new ContentChangeEvent(Document.this, comment.getParent(), new ContentRange(offset, offset + adjustedText.length() - 1), false));
 			}
 		});
 	}
@@ -304,7 +305,7 @@
 
 		final Parent parent = getParentForInsertionAt(offset);
 
-		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1)));
+		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1), true));
 
 		final Comment comment = new Comment();
 		getContent().insertTagMarker(offset);
@@ -313,7 +314,7 @@
 
 		parent.insertChildAt(offset, comment);
 
-		fireContentInserted(new ContentChangeEvent(this, parent, comment.getRange()));
+		fireContentInserted(new ContentChangeEvent(this, parent, comment.getRange(), true));
 
 		return comment;
 	}
@@ -332,7 +333,7 @@
 			throw new DocumentValidationException(MessageFormat.format("Cannot insert element {0} at offset {1}.", elementName, offset));
 		}
 
-		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1)));
+		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1), true));
 
 		final Element element = new Element(elementName);
 		getContent().insertTagMarker(offset);
@@ -341,7 +342,7 @@
 
 		parent.insertChildAt(offset, element);
 
-		fireContentInserted(new ContentChangeEvent(this, parent, element.getRange()));
+		fireContentInserted(new ContentChangeEvent(this, parent, element.getRange(), true));
 
 		return element;
 	}
@@ -359,7 +360,8 @@
 			throw new DocumentValidationException(MessageFormat.format("Cannot insert document fragment at offset {0}.", offset));
 		}
 
-		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1)));
+		final boolean textOnly = fragment.children().withoutText().isEmpty();
+		fireBeforeContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + 1), !textOnly));
 
 		getContent().insertContent(offset, fragment.getContent());
 
@@ -378,7 +380,7 @@
 
 		declareNamespaces(undeclaredNamespaces, parent);
 
-		fireContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + fragment.getContent().length() - 1)));
+		fireContentInserted(new ContentChangeEvent(this, parent, new ContentRange(offset, offset + fragment.getContent().length() - 1), !textOnly));
 	}
 
 	private void associateDeeply(final Node node, final int offset) {
@@ -476,16 +478,18 @@
 			throw new DocumentValidationException(MessageFormat.format("Cannot delete {0}", range));
 		}
 
-		fireBeforeContentDeleted(new ContentChangeEvent(this, parentForDeletion, range));
+		// Use IAxis#withoutText here, there is no need to create Text nodes for deletion
+		final List<? extends INode> childrenToDelete = parentForDeletion.children().withoutText().in(range).asList();
+		fireBeforeContentDeleted(new ContentChangeEvent(this, parentForDeletion, range, !childrenToDelete.isEmpty()));
 
-		for (final INode child : parentForDeletion.children().in(range).asList()) {
+		for (final INode child : childrenToDelete) {
 			parentForDeletion.removeChild((Node) child);
 			((Node) child).dissociate();
 		}
 
 		getContent().remove(range);
 
-		fireContentDeleted(new ContentChangeEvent(this, parentForDeletion, range));
+		fireContentDeleted(new ContentChangeEvent(this, parentForDeletion, range, !childrenToDelete.isEmpty()));
 	}
 
 	/*
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/ContentChangeEvent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/ContentChangeEvent.java
index 870227d..7218320 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/ContentChangeEvent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/ContentChangeEvent.java
@@ -7,6 +7,7 @@
  * 

  * Contributors:

  * 		Florian Thienel - initial API and implementation

+ * 		Carsten Hiesserich - added structuralChange flag

  *******************************************************************************/

 package org.eclipse.vex.core.provisional.dom;

 

@@ -20,6 +21,7 @@
 	private static final long serialVersionUID = 1L;

 

 	private final ContentRange range;

+	private final boolean structuralChange;

 

 	/**

 	 * Create an event.

@@ -30,10 +32,12 @@
 	 *            the parent node containing the change

 	 * @param range

 	 *            the range which was changed

+	 * @param structuralChange

+	 *            <code>true</code> if the structure is changed (childs added or removed)

 	 */

-	public ContentChangeEvent(final IDocument document, final IParent parent, final ContentRange range) {

+	public ContentChangeEvent(final IDocument document, final IParent parent, final ContentRange range, final boolean structuralChange) {

 		super(document, parent);

-

+		this.structuralChange = structuralChange;

 		this.range = range;

 	}

 

@@ -44,4 +48,11 @@
 		return range;

 	}

 

+	/**

+	 * @return <code>true</code> when this event has been triggered by a structural change (childs added or removed).

+	 */

+	public boolean isStructuralChange() {

+		return structuralChange;

+	}

+

 }