LineMap.java

package org.jsoup.internal;

import java.util.Arrays;

/**
 Maps source offsets to line and column coordinates. Jsoup internal; API subject to change.
 */
public final class LineMap {
    private static final int InitialLineCapacity = 16;
    private static final int CapacityGrowthFactor = 2;
    private static final int[] Empty = new int[0];
    private int[] lineStarts = Empty;
    private int size;

    /**
     Creates a shared line map for tracked source ranges.
     */
    public LineMap() {}

    /**
     Records the source offset immediately after a newline.
     */
    public void addLineStart(int pos) {
        // Buffer scans overlap after compaction, so ignore line starts already recorded.
        if (size == 0 || pos > lineStarts[size - 1]) {
            ensureCapacity(size + 1);
            lineStarts[size++] = pos;
        }
    }

    /**
     Trims the backing array after the parse has completed.
     */
    public void complete() {
        if (lineStarts.length != size)
            lineStarts = size == 0 ? Empty : Arrays.copyOf(lineStarts, size);
    }

    /**
     Gets the 1-based line number for a source offset.
     */
    public int lineNumber(int pos) {
        if (pos < 0)
            return -1;
        int lineIndex = lineStartIndex(pos);
        // -1 means before the first newline, so line 1. lineStarts[0] is the start of line 2.
        return lineIndex + 2;
    }

    /**
     Gets the 1-based column number for a source offset.
     */
    public int columnNumber(int pos) {
        if (pos < 0)
            return -1;
        int index = lineStartIndex(pos);
        return index == -1 ? pos + 1 : pos - lineStarts[index] + 1;
    }

    /**
     Finds the preceding line-start entry for a source offset.
     */
    private int lineStartIndex(int pos) {
        int index = Arrays.binarySearch(lineStarts, 0, size, pos);
        return index >= 0 ? index : -index - 2;
    }

    /**
     Grows line-start storage with a small initial allocation and doubling growth.
     */
    private void ensureCapacity(int minSize) {
        if (lineStarts.length >= minSize)
            return;
        int newSize = lineStarts.length == 0 ? InitialLineCapacity : lineStarts.length * CapacityGrowthFactor;
        if (newSize < minSize)
            newSize = minSize;
        lineStarts = Arrays.copyOf(lineStarts, newSize);
    }
}