|  | // Copyright 2014 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package com.google.devtools.build.lib.syntax; | 
|  |  | 
|  | import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | 
|  | import com.google.devtools.build.lib.events.Location.LineAndColumn; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; | 
|  | import com.google.devtools.build.lib.util.Pair; | 
|  | import com.google.devtools.build.lib.vfs.PathFragment; | 
|  | import java.io.Serializable; | 
|  | import java.util.Arrays; | 
|  | import java.util.Objects; | 
|  |  | 
|  | /** | 
|  | * A table to keep track of line numbers in source files. The client creates a LineNumberTable for | 
|  | * their buffer using {@link #create}. The client can then ask for the line and column given a | 
|  | * position using ({@link #getLineAndColumn(int)}). | 
|  | */ | 
|  | @AutoCodec | 
|  | @Immutable | 
|  | public class LineNumberTable implements Serializable { | 
|  | public static final ObjectCodec<LineNumberTable> CODEC = new LineNumberTable_AutoCodec(); | 
|  |  | 
|  | /** A mapping from line number (line >= 1) to character offset into the file. */ | 
|  | private final int[] linestart; | 
|  |  | 
|  | private final PathFragment path; | 
|  | private final int bufferLength; | 
|  |  | 
|  | public LineNumberTable(char[] buffer, PathFragment path) { | 
|  | this(computeLinestart(buffer), path, buffer.length); | 
|  | } | 
|  |  | 
|  | @AutoCodec.Instantiator | 
|  | LineNumberTable(int[] linestart, PathFragment path, int bufferLength) { | 
|  | this.linestart = linestart; | 
|  | this.path = path; | 
|  | this.bufferLength = bufferLength; | 
|  | } | 
|  |  | 
|  | static LineNumberTable create(char[] buffer, PathFragment path) { | 
|  | return new LineNumberTable(buffer, path); | 
|  | } | 
|  |  | 
|  | private int getLineAt(int offset) { | 
|  | if (offset < 0) { | 
|  | throw new IllegalStateException("Illegal position: " + offset); | 
|  | } | 
|  | int lowBoundary = 1; | 
|  | int highBoundary = linestart.length - 1; | 
|  | while (true) { | 
|  | if ((highBoundary - lowBoundary) <= 1) { | 
|  | if (linestart[highBoundary] > offset) { | 
|  | return lowBoundary; | 
|  | } else { | 
|  | return highBoundary; | 
|  | } | 
|  | } | 
|  | int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1); | 
|  | if (linestart[medium] > offset) { | 
|  | highBoundary = medium; | 
|  | } else { | 
|  | lowBoundary = medium; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | LineAndColumn getLineAndColumn(int offset) { | 
|  | int line = getLineAt(offset); | 
|  | int column = offset - linestart[line] + 1; | 
|  | return new LineAndColumn(line, column); | 
|  | } | 
|  |  | 
|  | PathFragment getPath(int offset) { | 
|  | return path; | 
|  | } | 
|  |  | 
|  | Pair<Integer, Integer> getOffsetsForLine(int line) { | 
|  | if (line <= 0 || line >= linestart.length) { | 
|  | throw new IllegalArgumentException("Illegal line: " + line); | 
|  | } | 
|  | return Pair.of( | 
|  | linestart[line], line < linestart.length - 1 ? linestart[line + 1] : bufferLength); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return Objects.hash(Arrays.hashCode(linestart), path, bufferLength); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object other) { | 
|  | if (other == null || !other.getClass().equals(getClass())) { | 
|  | return false; | 
|  | } | 
|  | LineNumberTable that = (LineNumberTable) other; | 
|  | return this.bufferLength == that.bufferLength | 
|  | && Arrays.equals(this.linestart, that.linestart) | 
|  | && Objects.equals(this.path, that.path); | 
|  | } | 
|  |  | 
|  | private static int[] computeLinestart(char[] buffer) { | 
|  | // Compute the size. | 
|  | int size = 2; | 
|  | for (int i = 0; i < buffer.length; i++) { | 
|  | if (buffer[i] == '\n') { | 
|  | size++; | 
|  | } | 
|  | } | 
|  | int[] linestart = new int[size]; | 
|  |  | 
|  | int index = 0; | 
|  | linestart[index++] = 0; // The 0th line does not exist - so we fill something in | 
|  | // to make sure the start pos for the 1st line ends up at | 
|  | // linestart[1]. Using 0 is useful for tables that are | 
|  | // completely empty. | 
|  | linestart[index++] = 0; // The first line ("line 1") starts at offset 0. | 
|  |  | 
|  | // Scan the buffer and record the offset of each line start. Doing this | 
|  | // once upfront is faster than checking each char as it is pulled from | 
|  | // the buffer. | 
|  | for (int i = 0; i < buffer.length; i++) { | 
|  | if (buffer[i] == '\n') { | 
|  | linestart[index++] = i + 1; | 
|  | } | 
|  | } | 
|  | return linestart; | 
|  | } | 
|  | } |