aoqi@0: /*
aoqi@0: * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0: *
aoqi@0: * This code is free software; you can redistribute it and/or modify it
aoqi@0: * under the terms of the GNU General Public License version 2 only, as
aoqi@0: * published by the Free Software Foundation. Oracle designates this
aoqi@0: * particular file as subject to the "Classpath" exception as provided
aoqi@0: * by Oracle in the LICENSE file that accompanied this code.
aoqi@0: *
aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0: * accompanied this code).
aoqi@0: *
aoqi@0: * You should have received a copy of the GNU General Public License version
aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0: *
aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0: * or visit www.oracle.com if you need additional information or have any
aoqi@0: * questions.
aoqi@0: */
aoqi@0:
aoqi@0: package com.sun.tools.javac.util;
aoqi@0:
aoqi@0: import java.util.BitSet;
aoqi@0: import static com.sun.tools.javac.util.LayoutCharacters.*;
aoqi@0:
aoqi@0: /** A class that defines source code positions as simple character
aoqi@0: * offsets from the beginning of the file. The first character
aoqi@0: * is at position 0.
aoqi@0: *
aoqi@0: * Support is also provided for (line,column) coordinates, but tab
aoqi@0: * expansion is optional and no Unicode excape translation is considered.
aoqi@0: * The first character is at location (1,1).
aoqi@0: *
aoqi@0: *
This is NOT part of any supported API.
aoqi@0: * If you write code that depends on this, you do so at your own risk.
aoqi@0: * This code and its internal interfaces are subject to change or
aoqi@0: * deletion without notice.
aoqi@0: */
aoqi@0: public class Position {
aoqi@0: public static final int NOPOS = -1;
aoqi@0:
aoqi@0: public static final int FIRSTPOS = 0;
aoqi@0: public static final int FIRSTLINE = 1;
aoqi@0: public static final int FIRSTCOLUMN = 1;
aoqi@0:
aoqi@0: public static final int LINESHIFT = 10;
aoqi@0: public static final int MAXCOLUMN = (1<
aoqi@0: * Notes: The first character position FIRSTPOS is at
aoqi@0: * (FIRSTLINE,FIRSTCOLUMN). No account is taken of Unicode escapes.
aoqi@0: *
aoqi@0: * @param src Source characters
aoqi@0: * @param max Number of characters to read
aoqi@0: * @param expandTabs If true, expand tabs when calculating columns
aoqi@0: */
aoqi@0: public static LineMap makeLineMap(char[] src, int max, boolean expandTabs) {
aoqi@0: LineMapImpl lineMap = expandTabs ?
aoqi@0: new LineTabMapImpl(max) : new LineMapImpl();
aoqi@0: lineMap.build(src, max);
aoqi@0: return lineMap;
aoqi@0: }
aoqi@0:
aoqi@0: /** Encode line and column numbers in an integer as:
aoqi@0: * {@code line-number << LINESHIFT + column-number }.
aoqi@0: * {@link Position#NOPOS} represents an undefined position.
aoqi@0: *
aoqi@0: * @param line number of line (first is 1)
aoqi@0: * @param col number of character on line (first is 1)
aoqi@0: * @return an encoded position or {@link Position#NOPOS}
aoqi@0: * if the line or column number is too big to
aoqi@0: * represent in the encoded format
aoqi@0: * @throws IllegalArgumentException if line or col is less than 1
aoqi@0: */
aoqi@0: public static int encodePosition(int line, int col) {
aoqi@0: if (line < 1)
aoqi@0: throw new IllegalArgumentException("line must be greater than 0");
aoqi@0: if (col < 1)
aoqi@0: throw new IllegalArgumentException("column must be greater than 0");
aoqi@0:
aoqi@0: if (line > MAXLINE || col > MAXCOLUMN) {
aoqi@0: return NOPOS;
aoqi@0: }
aoqi@0: return (line << LINESHIFT) + col;
aoqi@0: }
aoqi@0:
aoqi@0: public static interface LineMap extends com.sun.source.tree.LineMap {
aoqi@0: /** Find the start position of a line.
aoqi@0: *
aoqi@0: * @param line number of line (first is 1)
aoqi@0: * @return position of first character in line
aoqi@0: * @throws ArrayIndexOutOfBoundsException
aoqi@0: * if {@code lineNumber < 1}
aoqi@0: * if {@code lineNumber > no. of lines}
aoqi@0: */
aoqi@0: int getStartPosition(int line);
aoqi@0:
aoqi@0: /** Find the position corresponding to a (line,column).
aoqi@0: *
aoqi@0: * @param line number of line (first is 1)
aoqi@0: * @param column number of character on line (first is 1)
aoqi@0: *
aoqi@0: * @return position of character
aoqi@0: * @throws ArrayIndexOutOfBoundsException
aoqi@0: * if {@code line < 1}
aoqi@0: * if {@code line > no. of lines}
aoqi@0: */
aoqi@0: int getPosition(int line, int column);
aoqi@0:
aoqi@0: /** Find the line containing a position; a line termination
aoqi@0: * character is on the line it terminates.
aoqi@0: *
aoqi@0: * @param pos character offset of the position
aoqi@0: * @return the line number on which pos occurs (first line is 1)
aoqi@0: */
aoqi@0: int getLineNumber(int pos);
aoqi@0:
aoqi@0: /** Find the column for a character position.
aoqi@0: * Note: this method does not handle tab expansion.
aoqi@0: * If tab expansion is needed, use a LineTabMap instead.
aoqi@0: *
aoqi@0: * @param pos character offset of the position
aoqi@0: * @return the column number at which pos occurs
aoqi@0: */
aoqi@0: int getColumnNumber(int pos);
aoqi@0: }
aoqi@0:
aoqi@0: static class LineMapImpl implements LineMap {
aoqi@0: protected int[] startPosition; // start position of each line
aoqi@0:
aoqi@0: protected LineMapImpl() {}
aoqi@0:
aoqi@0: protected void build(char[] src, int max) {
aoqi@0: int c = 0;
aoqi@0: int i = 0;
aoqi@0: int[] linebuf = new int[max];
aoqi@0: while (i < max) {
aoqi@0: linebuf[c++] = i;
aoqi@0: do {
aoqi@0: char ch = src[i];
aoqi@0: if (ch == '\r' || ch == '\n') {
aoqi@0: if (ch == '\r' && (i+1) < max && src[i+1] == '\n')
aoqi@0: i += 2;
aoqi@0: else
aoqi@0: ++i;
aoqi@0: break;
aoqi@0: }
aoqi@0: else if (ch == '\t')
aoqi@0: setTabPosition(i);
aoqi@0: } while (++i < max);
aoqi@0: }
aoqi@0: this.startPosition = new int[c];
aoqi@0: System.arraycopy(linebuf, 0, startPosition, 0, c);
aoqi@0: }
aoqi@0:
aoqi@0: public int getStartPosition(int line) {
aoqi@0: return startPosition[line - FIRSTLINE];
aoqi@0: }
aoqi@0:
aoqi@0: public long getStartPosition(long line) {
aoqi@0: return getStartPosition(longToInt(line));
aoqi@0: }
aoqi@0:
aoqi@0: public int getPosition(int line, int column) {
aoqi@0: return startPosition[line - FIRSTLINE] + column - FIRSTCOLUMN;
aoqi@0: }
aoqi@0:
aoqi@0: public long getPosition(long line, long column) {
aoqi@0: return getPosition(longToInt(line), longToInt(column));
aoqi@0: }
aoqi@0:
aoqi@0: // Cache of last line number lookup
aoqi@0: private int lastPosition = Position.FIRSTPOS;
aoqi@0: private int lastLine = Position.FIRSTLINE;
aoqi@0:
aoqi@0: public int getLineNumber(int pos) {
aoqi@0: if (pos == lastPosition) {
aoqi@0: return lastLine;
aoqi@0: }
aoqi@0: lastPosition = pos;
aoqi@0:
aoqi@0: int low = 0;
aoqi@0: int high = startPosition.length-1;
aoqi@0: while (low <= high) {
aoqi@0: int mid = (low + high) >> 1;
aoqi@0: int midVal = startPosition[mid];
aoqi@0:
aoqi@0: if (midVal < pos)
aoqi@0: low = mid + 1;
aoqi@0: else if (midVal > pos)
aoqi@0: high = mid - 1;
aoqi@0: else {
aoqi@0: lastLine = mid + 1; // pos is at beginning of this line
aoqi@0: return lastLine;
aoqi@0: }
aoqi@0: }
aoqi@0: lastLine = low;
aoqi@0: return lastLine; // pos is on this line
aoqi@0: }
aoqi@0:
aoqi@0: public long getLineNumber(long pos) {
aoqi@0: return getLineNumber(longToInt(pos));
aoqi@0: }
aoqi@0:
aoqi@0: public int getColumnNumber(int pos) {
aoqi@0: return pos - startPosition[getLineNumber(pos) - FIRSTLINE] + FIRSTCOLUMN;
aoqi@0: }
aoqi@0:
aoqi@0: public long getColumnNumber(long pos) {
aoqi@0: return getColumnNumber(longToInt(pos));
aoqi@0: }
aoqi@0:
aoqi@0: private static int longToInt(long longValue) {
aoqi@0: int intValue = (int)longValue;
aoqi@0: if (intValue != longValue)
aoqi@0: throw new IndexOutOfBoundsException();
aoqi@0: return intValue;
aoqi@0: }
aoqi@0:
aoqi@0: protected void setTabPosition(int offset) {}
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * A LineMap that handles tab expansion correctly. The cost is
aoqi@0: * an additional bit per character in the source array.
aoqi@0: */
aoqi@0: public static class LineTabMapImpl extends LineMapImpl {
aoqi@0: private BitSet tabMap; // bits set for tab positions.
aoqi@0:
aoqi@0: public LineTabMapImpl(int max) {
aoqi@0: super();
aoqi@0: tabMap = new BitSet(max);
aoqi@0: }
aoqi@0:
aoqi@0: protected void setTabPosition(int offset) {
aoqi@0: tabMap.set(offset);
aoqi@0: }
aoqi@0:
aoqi@0: public int getColumnNumber(int pos) {
aoqi@0: int lineStart = startPosition[getLineNumber(pos) - FIRSTLINE];
aoqi@0: int column = 0;
aoqi@0: for (int bp = lineStart; bp < pos; bp++) {
aoqi@0: if (tabMap.get(bp))
aoqi@0: column = (column / TabInc * TabInc) + TabInc;
aoqi@0: else
aoqi@0: column++;
aoqi@0: }
aoqi@0: return column + FIRSTCOLUMN;
aoqi@0: }
aoqi@0:
aoqi@0: public int getPosition(int line, int column) {
aoqi@0: int pos = startPosition[line - FIRSTLINE];
aoqi@0: column -= FIRSTCOLUMN;
aoqi@0: int col = 0;
aoqi@0: while (col < column) {
aoqi@0: pos++;
aoqi@0: if (tabMap.get(pos))
aoqi@0: col = (col / TabInc * TabInc) + TabInc;
aoqi@0: else
aoqi@0: col++;
aoqi@0: }
aoqi@0: return pos;
aoqi@0: }
aoqi@0: }
aoqi@0: }