use a (sophisticated) chain of responsibility to handle visualization

The VisualizationChain knows three different kinds of NodeVisualization:
- for visualization of the RootBox
- for visualization of the document structure
- for visualization of inline content

For each kind of visualization, there is a prioritized list of possible
NodeVisualizations, that are requested in order of highest priority to
visualize a given node. The first NodeVisualization which returns an
actual box wins.

This architecture allows to have:
- individual visualizations for each document type
- easy extension with custom NodeVisualization implementations
- simple and decoupled building blocks for visualization

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
index 9804f87..11a9d5c 100644
--- a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
@@ -13,23 +13,31 @@
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.frame;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.paragraph;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.rootBox;
-import static org.eclipse.vex.core.internal.boxes.BoxFactory.square;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.staticText;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.verticalBlock;
 
+import java.util.TreeSet;
+
+import org.eclipse.core.runtime.QualifiedName;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.part.ViewPart;
-import org.eclipse.vex.core.internal.boxes.Border;
-import org.eclipse.vex.core.internal.boxes.BoxFactory;
-import org.eclipse.vex.core.internal.boxes.HorizontalBar;
-import org.eclipse.vex.core.internal.boxes.Margin;
-import org.eclipse.vex.core.internal.boxes.Padding;
+import org.eclipse.vex.core.internal.boxes.IBox;
+import org.eclipse.vex.core.internal.boxes.IChildBox;
+import org.eclipse.vex.core.internal.boxes.IInlineBox;
+import org.eclipse.vex.core.internal.boxes.IParentBox;
 import org.eclipse.vex.core.internal.boxes.Paragraph;
 import org.eclipse.vex.core.internal.boxes.RootBox;
-import org.eclipse.vex.core.internal.core.Color;
+import org.eclipse.vex.core.internal.boxes.VerticalBlock;
 import org.eclipse.vex.core.internal.core.FontSpec;
+import org.eclipse.vex.core.internal.dom.Document;
+import org.eclipse.vex.core.internal.dom.Element;
 import org.eclipse.vex.core.internal.widget.swt.BoxWidget;
+import org.eclipse.vex.core.provisional.dom.BaseNodeVisitorWithResult;
+import org.eclipse.vex.core.provisional.dom.IDocument;
+import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.INode;
+import org.eclipse.vex.core.provisional.dom.IText;
 
 /**
  * This is a viewer for the new box model - just to do visual experiments.
@@ -82,30 +90,163 @@
 	}
 
 	private RootBox createTestModel() {
-		final RootBox rootBox = rootBox();
+		final Document document = createTestDocument();
 
-		for (int i = 0; i < 10000; i += 1) {
-			rootBox.appendChild(frame(verticalBlock(horizontalBar(), mixedParagraph(i), horizontalBar()), new Margin(10), new Border(5), new Padding(10, 20, 30, 40)));
-			rootBox.appendChild(frame(verticalBlock(horizontalBar(), textParagraph(), horizontalBar()), new Margin(50), Border.NULL, Padding.NULL));
+		final VisualizationChain visualizationChain = buildVisualizationChain();
+
+		return visualizationChain.visualizeRoot(document);
+	}
+
+	private static Document createTestDocument() {
+		final Document document = new Document(new QualifiedName(null, "doc"));
+		for (int i = 0; i < 25000; i += 1) {
+			insertParagraph(document);
+		}
+		return document;
+	}
+
+	private static void insertParagraph(final Document document) {
+		final Element textElement = document.insertElement(document.getRootElement().getEndOffset(), new QualifiedName(null, "para"));
+		document.insertText(textElement.getEndOffset(), LOREM_IPSUM_LONG);
+	}
+
+	private static VisualizationChain buildVisualizationChain() {
+		final VisualizationChain visualizationChain = new VisualizationChain();
+		visualizationChain.addForRoot(new DocumentRootVisualization());
+		visualizationChain.addForStructure(new ParagraphVisualization());
+		visualizationChain.addForStructure(new StructureElementVisualization());
+		visualizationChain.addForInline(new TextVisualization());
+		return visualizationChain;
+	}
+
+	private static final class DocumentRootVisualization extends NodeVisualization<RootBox> {
+		@Override
+		public RootBox visit(final IDocument document) {
+			final RootBox result = rootBox();
+			visualizeChildrenStructure(document.children(), result);
+			return result;
+		}
+	}
+
+	private static final class StructureElementVisualization extends NodeVisualization<IChildBox> {
+		@Override
+		public IChildBox visit(final IElement element) {
+			final VerticalBlock component = verticalBlock();
+			visualizeChildrenStructure(element.children(), component);
+			return frame(component);
+		}
+	}
+
+	private static final class ParagraphVisualization extends NodeVisualization<IChildBox> {
+		public ParagraphVisualization() {
+			super(1);
 		}
 
-		return rootBox;
-	}
+		@Override
+		public IChildBox visit(final IElement element) {
+			if (!"para".equals(element.getLocalName())) {
+				return super.visit(element);
+			}
 
-	private HorizontalBar horizontalBar() {
-		return BoxFactory.horizontalBar(2, Color.BLACK);
-	}
-
-	private Paragraph mixedParagraph(final int i) {
-		final Paragraph paragraph = new Paragraph();
-		for (int j = 0; j < 20; j += 1) {
-			paragraph.appendChild(staticText("Lorem ipsum " + i + " ", new FontSpec(new String[] { "Arial" }, 0, 10.0f + j)));
-			paragraph.appendChild(square(5 + j));
+			final Paragraph paragraph = paragraph();
+			visualizeChildrenInline(element.children(), paragraph);
+			return frame(paragraph);
 		}
-		return paragraph;
 	}
 
-	private Paragraph textParagraph() {
-		return paragraph(staticText(LOREM_IPSUM_LONG, TIMES_NEW_ROMAN));
+	private static final class TextVisualization extends NodeVisualization<IInlineBox> {
+		@Override
+		public IInlineBox visit(final IText text) {
+			return staticText(text.getText(), TIMES_NEW_ROMAN);
+		}
+	}
+
+	private static class NodeVisualization<T extends IBox> extends BaseNodeVisitorWithResult<T> implements Comparable<NodeVisualization<?>> {
+		private final int priority;
+		private VisualizationChain chain;
+
+		public NodeVisualization() {
+			this(0);
+		}
+
+		public NodeVisualization(final int priority) {
+			this.priority = priority;
+		}
+
+		public final T visualize(final INode node) {
+			return node.accept(this);
+		}
+
+		@Override
+		public final int compareTo(final NodeVisualization<?> other) {
+			return other.priority - priority;
+		}
+
+		public final void setChain(final VisualizationChain chain) {
+			this.chain = chain;
+		}
+
+		protected final void visualizeChildrenStructure(final Iterable<INode> children, final IParentBox<IChildBox> parentBox) {
+			for (final INode child : children) {
+				final IChildBox childBox = chain.visualizeStructure(child);
+				if (childBox != null) {
+					parentBox.appendChild(childBox);
+				}
+			}
+		}
+
+		protected final void visualizeChildrenInline(final Iterable<INode> children, final IParentBox<IInlineBox> parentBox) {
+			for (final INode child : children) {
+				final IInlineBox childBox = chain.visualizeInline(child);
+				if (childBox != null) {
+					parentBox.appendChild(childBox);
+				}
+			}
+		}
+	}
+
+	private static final class VisualizationChain {
+		private final TreeSet<NodeVisualization<RootBox>> rootChain = new TreeSet<NodeVisualization<RootBox>>();
+		private final TreeSet<NodeVisualization<IChildBox>> structureChain = new TreeSet<NodeVisualization<IChildBox>>();
+		private final TreeSet<NodeVisualization<IInlineBox>> inlineChain = new TreeSet<NodeVisualization<IInlineBox>>();
+
+		public RootBox visualizeRoot(final INode node) {
+			return visualize(node, rootChain);
+		}
+
+		public IChildBox visualizeStructure(final INode node) {
+			return visualize(node, structureChain);
+		}
+
+		public IInlineBox visualizeInline(final INode node) {
+			return visualize(node, inlineChain);
+		}
+
+		private static <T extends IBox> T visualize(final INode node, final TreeSet<NodeVisualization<T>> chain) {
+			for (final NodeVisualization<T> visualization : chain) {
+				final T box = visualization.visualize(node);
+				if (box != null) {
+					return box;
+				}
+			}
+			return null;
+		}
+
+		public void addForRoot(final NodeVisualization<RootBox> visualization) {
+			add(visualization, rootChain);
+		}
+
+		public void addForStructure(final NodeVisualization<IChildBox> visualization) {
+			add(visualization, structureChain);
+		}
+
+		public void addForInline(final NodeVisualization<IInlineBox> visualization) {
+			add(visualization, inlineChain);
+		}
+
+		private <T extends IBox> void add(final NodeVisualization<T> visualization, final TreeSet<NodeVisualization<T>> chain) {
+			chain.add(visualization);
+			visualization.setChain(this);
+		}
 	}
 }