Bug 560686: Add BasicHeuristicTokenScanner.scanForward for multiple
strings

Change-Id: Ib15024535b02b438e3418bd7aa28964a39e6a12b
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ecommons/text/BasicHeuristicTokenScanner.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ecommons/text/BasicHeuristicTokenScanner.java
index f70392f..11eb491 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ecommons/text/BasicHeuristicTokenScanner.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ecommons/text/BasicHeuristicTokenScanner.java
@@ -25,6 +25,8 @@
 
 import org.eclipse.statet.jcommons.collections.IntArrayList;
 import org.eclipse.statet.jcommons.collections.IntList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
 
 import org.eclipse.statet.ecommons.text.core.PartitionConstraint;
 import org.eclipse.statet.ecommons.text.core.sections.DocContentSections;
@@ -37,6 +39,7 @@
  * 
  * @since 0.2
  */
+@NonNullByDefault
 public class BasicHeuristicTokenScanner implements ITokenScanner {
 	
 	
@@ -84,7 +87,7 @@
 	 */
 	protected abstract class PartitionBasedCondition extends StopCondition {
 		
-		private ITypedRegion currentPartition;
+		private @Nullable ITypedRegion currentPartition;
 		private boolean currentPartitionMatched;
 		private int currentPartitionStart;
 		private int currentPartitionEnd;
@@ -98,10 +101,11 @@
 			if (this.currentPartitionMatched && this.currentPartitionStart <= BasicHeuristicTokenScanner.this.pos && BasicHeuristicTokenScanner.this.pos < this.currentPartitionEnd) {
 				return matchesChar();
 			}
-			this.currentPartition= getPartition();
-			this.currentPartitionStart= this.currentPartition.getOffset();
-			this.currentPartitionEnd= this.currentPartitionStart+this.currentPartition.getLength();
-			if (BasicHeuristicTokenScanner.this.partitionConstraint.matches(this.currentPartition.getType())) {
+			final ITypedRegion partition= getPartition();
+			this.currentPartition= partition;
+			this.currentPartitionStart= partition.getOffset();
+			this.currentPartitionEnd= this.currentPartitionStart + partition.getLength();
+			if (BasicHeuristicTokenScanner.this.partitionConstraint.matches(partition.getType())) {
 				this.currentPartitionMatched= true;
 				return matchesChar();
 			}
@@ -209,36 +213,48 @@
 	
 	protected class StringMatchCondition extends PartitionBasedCondition {
 		
-		protected final String fString;
-		private final char fEscapeChar;
-		private int fLastEscapeOffset= -100;
+		protected final String[] strings;
+		private final int escapeChar;
+		private int lastEscapeOffset= Integer.MIN_VALUE;
 		
 		/**
 		 * Creates a new instance.
 		 * @param ch the string to match
 		 */
-		public StringMatchCondition(final String s, final char escapeChar) {
-			this.fString= s;
-			this.fEscapeChar= escapeChar;
+		public StringMatchCondition(final String s, final int escapeChar) {
+			this.strings= new String[] { s };
+			this.escapeChar= escapeChar;
+		}
+		
+		/**
+		 * Creates a new instance.
+		 * @param ch the string to match
+		 */
+		public StringMatchCondition(final String[] s, final int escapeChar) {
+			this.strings= s;
+			this.escapeChar= escapeChar;
 		}
 		
 		@Override
 		protected boolean matchesChar() {
-			if (BasicHeuristicTokenScanner.this.pos == this.fLastEscapeOffset+1) {
+			if (BasicHeuristicTokenScanner.this.pos == this.lastEscapeOffset + 1) {
 				return false;
 			}
-			if (this.fString.charAt(0) == BasicHeuristicTokenScanner.this.ch) {
-				try {
-					if (this.fString.regionMatches(1, BasicHeuristicTokenScanner.this.document.get(BasicHeuristicTokenScanner.this.pos+1, this.fString.length()-1),
-							0, this.fString.length()-1 )) {
-						return true;
+			if (this.escapeChar == BasicHeuristicTokenScanner.this.ch) {
+				this.lastEscapeOffset= BasicHeuristicTokenScanner.this.pos;
+				return false;
+			}
+			try {
+				for (final String string : this.strings) {
+					if (string.charAt(0) == BasicHeuristicTokenScanner.this.ch) {
+						if (string.regionMatches(1, BasicHeuristicTokenScanner.this.document.get(
+								BasicHeuristicTokenScanner.this.pos + 1, string.length() - 1),
+								0, string.length() - 1 )) {
+							return true;
+						}
 					}
-				} catch (final BadLocationException e) {}
-			}
-			if (this.fEscapeChar == BasicHeuristicTokenScanner.this.ch) {
-				this.fLastEscapeOffset= BasicHeuristicTokenScanner.this.pos;
-				return false;
-			}
+				}
+			} catch (final BadLocationException e) {}
 			return false;
 		}
 		
@@ -264,10 +280,11 @@
 	/** the most recently read line of position (only if used). */
 	private int line;
 	
-	private StopCondition nonWSCondition;
-	private StopCondition nonWSorLRCondition;
+	private @Nullable StopCondition nonWSCondition;
+	private @Nullable StopCondition nonWSorLRCondition;
 	
 	
+	@SuppressWarnings("null")
 	public BasicHeuristicTokenScanner(final DocContentSections documentContentInfo,
 			final PartitionConstraint defaultContentConstraint) {
 		this.partitioning= documentContentInfo.getPartitioning();
@@ -300,51 +317,59 @@
 	}
 	
 	protected final StopCondition getAnyNonWSCondition() {
-		if (this.nonWSCondition == null) {
-			this.nonWSCondition= new StopCondition() {
+		StopCondition nonWSCondition= this.nonWSCondition;
+		if (nonWSCondition == null) {
+			nonWSCondition= new StopCondition() {
 				@Override
 				public boolean stop() {
 					return (!isWhitespace());
 				}
 			};
+			this.nonWSCondition= nonWSCondition;
 		}
-		return this.nonWSCondition;
+		return nonWSCondition;
 	}
 	
 	protected final StopCondition getAnyNonWSorLRCondition() {
-		if (this.nonWSorLRCondition == null) {
-			this.nonWSorLRCondition= new StopCondition() {
+		StopCondition nonWSorLRCondition= this.nonWSorLRCondition;
+		if (nonWSorLRCondition == null) {
+			nonWSorLRCondition= new StopCondition() {
 				@Override
 				public boolean stop() {
 					return (!isWhitespace() && BasicHeuristicTokenScanner.this.ch != '\r' && BasicHeuristicTokenScanner.this.ch != '\n');
 				}
 			};
+			this.nonWSorLRCondition= nonWSorLRCondition;
 		}
-		return this.nonWSorLRCondition;
+		return nonWSorLRCondition;
 	}
 	
 	protected final StopCondition getNonWSCondition() {
-		if (this.nonWSCondition == null) {
-			this.nonWSCondition= new PartitionBasedCondition() {
+		StopCondition nonWSCondition= this.nonWSCondition;
+		if (nonWSCondition == null) {
+			nonWSCondition= new PartitionBasedCondition() {
 				@Override
 				protected boolean matchesChar() {
 					return (!isWhitespace());
 				}
 			};
+			this.nonWSCondition= nonWSCondition;
 		}
-		return this.nonWSCondition;
+		return nonWSCondition;
 	}
 	
 	protected final StopCondition getNonWSorLRCondition() {
-		if (this.nonWSorLRCondition == null) {
-			this.nonWSorLRCondition= new PartitionBasedCondition() {
+		StopCondition nonWSorLRCondition= this.nonWSorLRCondition;
+		if (nonWSorLRCondition == null) {
+			nonWSorLRCondition= new PartitionBasedCondition() {
 				@Override
 				protected boolean matchesChar() {
 					return (!isWhitespace() && BasicHeuristicTokenScanner.this.ch != '\r' && BasicHeuristicTokenScanner.this.ch != '\n');
 				}
 			};
+			this.nonWSorLRCondition= nonWSorLRCondition;
 		}
-		return this.nonWSorLRCondition;
+		return nonWSorLRCondition;
 	}
 	
 	/**
@@ -621,11 +646,11 @@
 				getNonWSorLRCondition() : getNonWSCondition());
 	}
 	
-	public IRegion findBlankRegion(final int position, final boolean linebreakIsBlank) {
+	public @Nullable IRegion findBlankRegion(final int position, final boolean linebreakIsBlank) {
 		return findRegion(position, linebreakIsBlank ?
 				getAnyNonWSorLRCondition() : getAnyNonWSCondition());
 	}
-		
+	
 	public boolean isBlankLine(final int position) throws BadLocationException {
 		final IRegion line= this.document.getLineInformationOfOffset(position);
 		if (line.getLength() > 0) {
@@ -635,7 +660,7 @@
 		return true;
 	}
 	
-	public final IRegion findCommonWord(final int position) {
+	public final @Nullable IRegion findCommonWord(final int position) {
 		return findRegion(position, new StopCondition() {
 			@Override
 			public boolean stop() {
@@ -657,7 +682,7 @@
 	}
 	
 	
-	private final int[] preScanBackward(final int start, final int bound, final StopCondition condition) throws BadLocationException {
+	private final int @Nullable [] preScanBackward(final int start, final int bound, final StopCondition condition) throws BadLocationException {
 		final IntList list= new IntArrayList();
 		int scanEnd= start+1;
 		
@@ -747,14 +772,20 @@
 		return scanForward(position, bound, new CharacterMatchCondition(chars));
 	}
 	
-	/** Provisional / Not Tested */
-	public final int scanForward(final int position, int bound, final String s, final char escapeChar) {
+	public final int scanForward(final int position, int bound, final String s, final int escapeChar) {
 		if (bound == UNBOUND) {
 			bound= this.document.getLength();
 		}
 		bound-= s.length();
 		return scanForward(position, bound, new StringMatchCondition(s, escapeChar));
 	}
+	public final int scanForward(final int position, int bound, final String[] s, final int escapeChar) {
+		if (bound == UNBOUND) {
+			bound= this.document.getLength();
+		}
+		bound-= s[0].length();
+		return scanForward(position, bound, new StringMatchCondition(s, escapeChar));
+	}
 	
 	/**
 	 * Finds the highest position <code>p</code> in <code>document</code> such that <code>bound</code> &lt; <code>p</code> &lt;= <code>start</code>
@@ -831,11 +862,11 @@
 	}
 	
 	
-	protected final IRegion findRegion(final int position, final StopCondition condition) {
+	protected final @Nullable IRegion findRegion(final int position, final StopCondition condition) {
 		return findRegion(position, condition, false);
 	}
 	
-	protected final IRegion findRegion(final int position, final StopCondition condition, final boolean allowClosing) {
+	protected final @Nullable IRegion findRegion(final int position, final StopCondition condition, final boolean allowClosing) {
 		int start= position;
 		int end= scanForward(position, UNBOUND, condition);
 		if (end == NOT_FOUND) {
@@ -861,7 +892,7 @@
 	 * @return the content type at <code>position</code> or a dummy zero-length
 	 *     partition if accessing the document fails
 	 */
-	protected final String getContentType() {
+	protected final @Nullable String getContentType() {
 		try {
 			return TextUtilities.getContentType(this.document, this.partitioning, this.pos, false);
 		}