LineMapper.java
package org.mvel2.util;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
/**
 * @author Mike Brock
 */
public class LineMapper {
  private char[] expr;
  private ArrayList<Node> lineMapping;
  private Set<Integer> lines;
  public LineMapper(char[] expr) {
    this.expr = expr;
  }
  public LineLookup map() {
    lineMapping = new ArrayList<Node>();
    lines = new TreeSet<Integer>();
    int cursor = 0;
    int start = 0;
    int line = 1;
    for (; cursor < expr.length; cursor++) {
      switch (expr[cursor]) {
        case '\n':
          lines.add(line);
          lineMapping.add(new Node(start, cursor, line++));
          start = cursor + 1;
          break;
      }
    }
    if (cursor > start) {
      lines.add(line);
      lineMapping.add(new Node(start, cursor, line));
    }
    return new LineLookup() {
      public int getLineFromCursor(int cursor) {
        for (Node n : lineMapping) {
          if (n.isInRange(cursor)) {
            return n.getLine();
          }
        }
        return -1;
      }
      public boolean hasLine(int line) {
        return lines.contains(line);
      }
    };
  }
  public static interface LineLookup {
    public int getLineFromCursor(int cursor);
    public boolean hasLine(int line);
  }
  private static class Node implements Comparable<Node> {
    private int cursorStart;
    private int cursorEnd;
    private int line;
    private Node(int cursorStart, int cursorEnd, int line) {
      this.cursorStart = cursorStart;
      this.cursorEnd = cursorEnd;
      this.line = line;
    }
    public int getLine() {
      return line;
    }
    public boolean isInRange(int cursor) {
      return cursor >= cursorStart && cursor <= cursorEnd;
    }
    public int compareTo(Node node) {
      if (node.cursorStart >= cursorEnd) {
        return 1;
      } else if (node.cursorEnd < cursorStart) {
        return -1;
      } else {
        return 0;
      }
    }
  }
}