Better tokenizer usability
[svjatoslav_commons.git] / src / main / java / eu / svjatoslav / commons / string / tokenizer / Tokenizer.java
index 722e17a..cc20369 100755 (executable)
@@ -1,12 +1,7 @@
 /*
- * Svjatoslav Commons - shared library of common functionality.
- * Copyright ©2012-2017, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
- * 
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of version 3 of the GNU Lesser General Public License
- * or later as published by the Free Software Foundation.
+ * Svjatoslav Commons - shared library of common functionality. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
  */
-
 package eu.svjatoslav.commons.string.tokenizer;
 
 import java.util.ArrayList;
@@ -16,24 +11,35 @@ import java.util.stream.Stream;
 
 import static eu.svjatoslav.commons.string.tokenizer.Terminator.TerminationStrategy.DROP;
 import static eu.svjatoslav.commons.string.tokenizer.Terminator.TerminationStrategy.PRESERVE;
+import static java.lang.System.out;
 
 public class Tokenizer {
 
-    final Stack<Integer> tokenIndexes = new Stack<>();
+    /**
+     * Stack of token indexes. This allows to walk back in history and un-consume the token.
+     */
+    private final Stack<Integer> tokenIndexes = new Stack<>();
+
+    /**
+     * Terminators that will be searched for by given tokenizer within given source string.
+     */
     private final List<Terminator> terminators = new ArrayList<>();
-    private String source;
+
+    private String source; // string to be tokenized
+
     private int currentIndex = 0;
 
-    int cachedTerminatorIndex = -1;
-    Terminator cachedTerminator;
+    private int cachedTerminatorIndex = -1;
+    private Terminator cachedTerminator;
 
     public Tokenizer(final String source) {
         this.source = source;
     }
 
-    public Tokenizer(){}
+    public Tokenizer() {
+    }
 
-    public Tokenizer setSource(String source){
+    public Tokenizer setSource(String source) {
         this.source = source;
         currentIndex = 0;
         tokenIndexes.clear();
@@ -49,6 +55,11 @@ public class Tokenizer {
         return this;
     }
 
+    public Tokenizer addTerminator(Terminator terminator) {
+        terminators.add(terminator);
+        return this;
+    }
+
     public Tokenizer addTerminator(final String startSequence,
                                    final String endSequence, final Terminator.TerminationStrategy terminationStrategy) {
         terminators.add(new Terminator(startSequence, endSequence, terminationStrategy));
@@ -63,16 +74,18 @@ public class Tokenizer {
                     + "\" but got \"" + match.token + "\" instead.");
     }
 
-
-
+    /**
+     * @return next @TokenizerMatch or <code>null</code> if end of input is reached.
+     * @throws InvalidSyntaxException
+     */
     public TokenizerMatch getNextToken() throws InvalidSyntaxException {
         tokenIndexes.push(currentIndex);
 
         StringBuilder tokenAccumulator = new StringBuilder();
 
-        while (true){
+        while (true) {
 
-            if (currentIndex >= source.length()){ // reached end of input
+            if (currentIndex >= source.length()) { // reached end of input
                 if (hasAccumulatedToken(tokenAccumulator))
                     return new TokenizerMatch(tokenAccumulator.toString(), null, null);
                 else
@@ -89,7 +102,7 @@ public class Tokenizer {
 
             if (terminator.termination == PRESERVE)
                 return buildPreservedToken(tokenAccumulator, terminator);
-            else if (terminator.termination == DROP){
+            else if (terminator.termination == DROP) {
                 skipUntilTerminatorEnd(terminator);
 
                 if (hasAccumulatedToken(tokenAccumulator))
@@ -106,22 +119,26 @@ public class Tokenizer {
             currentIndex += terminator.startSequence.length();
     }
 
-    private TokenizerMatch buildPreservedToken(StringBuilder token, Terminator terminator) throws InvalidSyntaxException {
+    /**
+     * @throws InvalidSyntaxException if end sequence is not found as is expected by given token.
+     */
+    private TokenizerMatch buildPreservedToken(StringBuilder token, Terminator terminator)
+            throws InvalidSyntaxException {
         if (hasAccumulatedToken(token))
             return new TokenizerMatch(token.toString(), null, terminator);
 
         if (terminator.hasEndSequence())
-            return buildComplexPreservedToken(terminator);
+            return buildTokenWithExpectedENdSequence(terminator);
         else
-            return buildSimplePreservedToken(terminator);
+            return buildTokenWithoutEndSequence(terminator);
     }
 
-    private TokenizerMatch buildSimplePreservedToken(Terminator terminator) {
+    private TokenizerMatch buildTokenWithoutEndSequence(Terminator terminator) {
         currentIndex += terminator.startSequence.length();
         return new TokenizerMatch(terminator.startSequence, null, terminator);
     }
 
-    private TokenizerMatch buildComplexPreservedToken(Terminator terminator) throws InvalidSyntaxException {
+    private TokenizerMatch buildTokenWithExpectedENdSequence(Terminator terminator) throws InvalidSyntaxException {
         int endSequenceIndex = getEndSequenceIndex(terminator);
         String reminder = source.substring(currentIndex + terminator.startSequence.length(), endSequenceIndex);
         currentIndex = endSequenceIndex + terminator.endSequence.length();
@@ -129,6 +146,9 @@ public class Tokenizer {
         return new TokenizerMatch(terminator.startSequence, reminder, terminator);
     }
 
+    /**
+     * @throws InvalidSyntaxException if end of input is reached without finding expected end sequence.
+     */
     private int getEndSequenceIndex(Terminator terminator) throws InvalidSyntaxException {
         int endSequenceIndex = source.indexOf(terminator.endSequence,
                 currentIndex + terminator.startSequence.length());
@@ -147,7 +167,7 @@ public class Tokenizer {
         return getOrFindTokenTerminator() == null;
     }
 
-    public boolean hasMoreTokens(){
+    public boolean hasMoreContent() {
         return currentIndex < source.length();
     }
 
@@ -184,18 +204,43 @@ public class Tokenizer {
         return result;
     }
 
-    public boolean peekIsOneOf(String ... possibilities) throws InvalidSyntaxException {
+    public boolean peekIsOneOf(String... possibilities) throws InvalidSyntaxException {
         String nextToken = peekNextToken().token;
         return Stream.of(possibilities).anyMatch(possibility -> possibility.equals(nextToken));
     }
 
-    public void peekExpectNoneOf(String ... possibilities) throws InvalidSyntaxException {
+    public void peekExpectNoneOf(String... possibilities) throws InvalidSyntaxException {
         if (peekIsOneOf(possibilities))
             throw new InvalidSyntaxException("Not expected \"" + peekNextToken().token + "\" here.");
     }
-    
+
     public void unreadToken() {
         currentIndex = tokenIndexes.pop();
     }
 
+    /**
+     * For debugging
+     */
+    public void enlistRemainingTokens(){
+        int redTokenCount = 0;
+
+        try {
+            while (hasMoreContent()) {
+                out.println(getNextToken().toString());
+                redTokenCount++;
+            }
+        } catch (InvalidSyntaxException e){
+            out.println("There is syntax exception");
+        }
+
+        // restore pointer to original location
+        for (int i = 0; i< redTokenCount; i++ ) unreadToken();
+    }
+
+
+    public void skipUntilDataEnd() {
+        tokenIndexes.push(currentIndex);
+        currentIndex = source.length();
+    }
+
 }