Bug 521031 - [generic editor] How to implement Matching bracket

Example added to .project editor
Fix added to CompositeReconciler for dealing with strategy-less
reconcilers

Change-Id: I0b9597e5b25b4001c8e587b3dcdecb1aa3897cfd
Signed-off-by: Lucas Bullen <lbullen@redhat.com>
diff --git a/org.eclipse.ui.genericeditor.examples/plugin.xml b/org.eclipse.ui.genericeditor.examples/plugin.xml
index 2ce9beb..a4fd6f7 100644
--- a/org.eclipse.ui.genericeditor.examples/plugin.xml
+++ b/org.eclipse.ui.genericeditor.examples/plugin.xml
@@ -50,6 +50,10 @@
             class="org.eclipse.ui.genericeditor.examples.dotproject.FoldingReconciler"
             contentType="org.eclipse.ui.genericeditor.examples.dotproject">
       </reconciler>
+      <reconciler
+            class="org.eclipse.ui.genericeditor.examples.dotproject.BracketMatchingReconciler"
+            contentType="org.eclipse.ui.genericeditor.examples.dotproject">
+      </reconciler>
    </extension>
    <extension
          point="org.eclipse.ui.editors">
diff --git a/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/BracketMatchingReconciler.java b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/BracketMatchingReconciler.java
new file mode 100644
index 0000000..4ad7ba4
--- /dev/null
+++ b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/BracketMatchingReconciler.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Red Hat Inc. 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:
+ *     Lucas Bullen (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.ui.genericeditor.examples.dotproject;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension2;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.reconciler.IReconciler;
+import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
+import org.eclipse.jface.text.source.ICharacterPairMatcher;
+import org.eclipse.jface.text.source.MatchingCharacterPainter;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGBA;
+import org.eclipse.swt.widgets.Display;
+
+public class BracketMatchingReconciler implements IReconciler{
+	private RGBA fBoxingRGB = new RGBA(155, 155, 155, 50);
+	private MatchingCharacterPainter fMatchingCharacterPainter;
+	private SourceViewer fSourceViewer;
+	private ICharacterPairMatcher fCharacterPairMatcher = new ICharacterPairMatcher() {
+		@Override
+		public IRegion match(IDocument document, int offset){
+			try {
+				String before = document.get(0, offset);
+				String after = document.get(offset, document.getLength() - offset);
+				int closingIndex = after.indexOf('>');
+				int openingIndex = before.lastIndexOf('<');
+				int previousClosingIndex = after.indexOf('<');
+				int previousOpeningIndex = before.lastIndexOf('>');
+				if((previousClosingIndex != -1 && closingIndex > previousClosingIndex)
+						|| (previousOpeningIndex != -1 && openingIndex < previousOpeningIndex)) {
+					return null;
+				}
+				return new Region(openingIndex, offset - openingIndex + closingIndex + 1);
+			} catch (BadLocationException e) {
+				return null;
+			}
+		}
+
+		@Override
+		public int getAnchor() {
+			return ICharacterPairMatcher.RIGHT;
+		}
+
+		@Override
+		public void dispose() {
+			if(fMatchingCharacterPainter != null) {
+				fMatchingCharacterPainter.dispose();
+			}
+		}
+
+		@Override
+		public void clear() {
+			// No memory implemented
+		}
+	};
+
+	@Override
+	public void install(ITextViewer textViewer) {
+		if (textViewer instanceof ITextViewerExtension2 && textViewer instanceof SourceViewer) {
+			fSourceViewer = (SourceViewer)textViewer;
+			fMatchingCharacterPainter = new MatchingCharacterPainter(fSourceViewer, fCharacterPairMatcher);
+			fMatchingCharacterPainter.setColor(new Color (Display.getCurrent(), fBoxingRGB));
+			fMatchingCharacterPainter.setHighlightCharacterAtCaretLocation(true);
+			fMatchingCharacterPainter.setHighlightEnclosingPeerCharacters(true);
+			fSourceViewer.addPainter(fMatchingCharacterPainter);
+		}
+	}
+
+	@Override
+	public void uninstall() {
+		fSourceViewer.removePainter(fMatchingCharacterPainter);
+	}
+
+	@Override
+	public IReconcilingStrategy getReconcilingStrategy(String contentType) {
+		return null;
+	}
+}
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconciler.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconciler.java
index eb3f9f0..31b1362 100644
--- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconciler.java
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconciler.java
@@ -10,8 +10,10 @@
  *******************************************************************************/
 package org.eclipse.ui.internal.genericeditor;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import org.eclipse.jface.text.IDocumentExtension3;
 import org.eclipse.jface.text.ITextViewer;
@@ -23,7 +25,7 @@
 	private List<IReconciler> fReconcilers;
 
 	public CompositeReconciler(List<IReconciler> reconcilers) {
-		fReconcilers = reconcilers;
+		fReconcilers = reconcilers.stream().filter(Objects::nonNull).collect(Collectors.toList());
 	}
 
 	@Override
@@ -61,7 +63,19 @@
 
 	@Override
 	public IReconcilingStrategy getReconcilingStrategy(String contentType) {
-		return new CompositeReconcilerStrategy(fReconcilers, contentType);
+		List<IReconcilingStrategy> strategies = new ArrayList<>();
+		for (IReconciler iReconciler : fReconcilers) {
+			IReconcilingStrategy strategy = iReconciler.getReconcilingStrategy(contentType);
+			if(strategy != null) {
+				strategies.add(strategy);
+			}
+		}
+
+		if(strategies.size() == 1) {
+			return strategies.get(0);
+		}
+
+		return new CompositeReconcilerStrategy(strategies);
 
 	}
 }
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconcilerStrategy.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconcilerStrategy.java
index f302460..98699fc 100644
--- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconcilerStrategy.java
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeReconcilerStrategy.java
@@ -10,28 +10,20 @@
  *******************************************************************************/
 package org.eclipse.ui.internal.genericeditor;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.IRegion;
 import org.eclipse.jface.text.reconciler.DirtyRegion;
-import org.eclipse.jface.text.reconciler.IReconciler;
 import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
 import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
 
 public class CompositeReconcilerStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension{
 	private List<IReconcilingStrategy> fReconcilingStrategies;
 
-	public CompositeReconcilerStrategy(List<IReconciler> reconcilers, String contentType) {
-		this.fReconcilingStrategies = new ArrayList<>();
-		for (IReconciler iReconciler : reconcilers) {
-			IReconcilingStrategy strategy = iReconciler.getReconcilingStrategy(contentType);
-			if(strategy != null) {
-				fReconcilingStrategies.add(strategy);
-			}
-		}
+	public CompositeReconcilerStrategy(List<IReconcilingStrategy> strategies) {
+		this.fReconcilingStrategies = strategies;
 	}
 	@Override
 	public void setProgressMonitor(IProgressMonitor monitor) {