All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.hm.hafner.util.LineRangeList Maven / Gradle / Ivy

package edu.hm.hafner.util;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.stream.StreamSupport;

import com.google.errorprone.annotations.CanIgnoreReturnValue;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * {@link List} of {@link LineRange} that stores values more efficiently at runtime.
 *
 * 

* This class thinks of {@link LineRange} as two integers (start and end-start), hence a list of {@link LineRange} * becomes a list of integers. The class then stores those integers in {@code byte[]}. Each number is packed to UTF-8 * like variable length format. To store a long value N, we first split into 7 bit chunk, and store each 7 bit chunk as * a byte, in the little endian order. The last byte gets its 8th bit set to indicate that that's the last byte. Thus in * this format, 0x0 gets stored as 0x80, 0x1234 gets stored as {0x34,0xA4(0x24|0x80)}. *

* *

* This variable length mode stores data most efficiently, since most line numbers are small. Access characteristic gets * close to that of {@link LinkedList}, since we can only traverse this packed byte[] from the start or from the end. *

* * @author Kohsuke Kawaguchi */ public class LineRangeList extends AbstractList implements Serializable { private static final long serialVersionUID = -1123973098942984623L; private static final int DEFAULT_CAPACITY = 16; private static final boolean SEQUENTIAL = false; /** Encoded bits. */ private byte[] data; /** Number of bytes in {@link #data} that's already used. This is not {@link List#size()}. */ private int len; /** * Creates an empty {@link LineRangeList}. It uses a capacity of {@link LineRangeList#DEFAULT_CAPACITY}. */ public LineRangeList() { this(DEFAULT_CAPACITY); } /** * Creates an empty {@link LineRangeList} with the specified capacity. * * @param capacity * the initial capacity of the list */ public LineRangeList(final int capacity) { super(); data = new byte[capacity]; len = 0; } /** * Creates a new {@link LineRangeList} with the specified elements. * * @param copy * the initial elements */ public LineRangeList(final Collection copy) { this(copy.size() * 4); // guess addAll(copy); } /** * Creates a new {@link LineRangeList} with the specified elements. * * @param initialElements * the initial elements */ public LineRangeList(final LineRange... initialElements) { this(initialElements.length * 4); // guess addAll(Arrays.asList(initialElements)); } @Override public final boolean addAll(final Collection c) { return super.addAll(c); } /** * Appends all the elements in the specified collection to the end of this list, in the order that they are * returned by the specified collection's iterator (optional operation). The behavior of this operation is * undefined if the specified collection is modified while the operation is in progress. (Note that this will occur * if the specified collection is this list, and it's nonempty.) * * @param ranges * collection containing elements to be added to this list * * @return {@code true} if this list changed as a result of the call * @throws NullPointerException * if the specified collection contains one or more null elements and this list does not permit null * elements, or if the specified collection is null * @throws IllegalArgumentException * if some property of an element of the specified collection prevents it from being added to this list * @see #add(Object) */ public final boolean addAll(final Iterable ranges) { return StreamSupport.stream(ranges.spliterator(), SEQUENTIAL) .map(this::add) .reduce(Boolean::logicalOr) .orElse(false); } /** * Makes sure that the buffer has capability to store N bytes. * * @param n * capacity */ private void ensure(final int n) { if (data.length < n) { byte[] buf = new byte[Math.max(n, data.length * 2)]; System.arraycopy(data, 0, buf, 0, len); data = buf; } } @Override public boolean contains(final Object o) { if (o instanceof LineRange) { var other = (LineRange) o; for (var cursor = new Cursor(); cursor.hasNext();) { if (cursor.compare(other)) { return true; } } } return false; } @Override public LineRange get(final int index) { return new Cursor().skip(index).next(); } @Override public int size() { return new Cursor().count(); } @Override public LineRange set(final int index, final LineRange element) { return new Cursor().skip(index).rewrite(element); } @Override public void add(final int index, final LineRange element) { new Cursor().skip(index).add(element); } @Override public final boolean add(final LineRange lr) { new Cursor(len).add(lr); return true; } @Override public LineRange remove(final int index) { return new Cursor().skip(index).delete(); } @Override public void clear() { len = 0; } @NonNull @Override public Iterator iterator() { return new Cursor(); } @NonNull @Override public ListIterator listIterator() { return new Cursor(); } @NonNull @Override public ListIterator listIterator(final int index) { return new Cursor().skip(index); } /** * Minimizes the memory waste by throwing away excess capacity. */ public void trim() { if (len != data.length) { byte[] small = new byte[len]; System.arraycopy(data, 0, small, 0, len); data = small; } } /** * Navigates through the ranges and performs the conversion to and from {@link LineRange}. */ private class Cursor implements ListIterator { private int position; Cursor(final int position) { this.position = position; } Cursor() { this(0); } /** * Does the opposite of {@link #read()} and skips back one int. */ private void prev() { if (position == 0) { throw new NoSuchElementException("Cursor is a the beginning."); } do { position--; } while (position > 0 && (data[position - 1] & 0x80) == 0); } /** * Reads the {@link LineRange} object the cursor is pointing at. * * @return the current element */ @Override @SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "thrown in read()") public LineRange next() { int s = read(); int d = read(); return new LineRange(s, s + d); } @Override public LineRange previous() { prev(); prev(); return copy().next(); } /** * Removes the last returned value. */ @Override public void remove() { prev(); prev(); delete(); } @Override public boolean hasNext() { return position < len; } @Override public boolean hasPrevious() { return position > 0; } /** * Reads the current variable-length encoded int value under the cursor, and moves the cursor ahead. * * @return the current value * @throws NoSuchElementException if encoded buffer contains no more element */ private int read() throws NoSuchElementException { if (len <= position) { throw new NoSuchElementException("Position " + position + " is >= length " + len); } int i = 0; int v = 0; do { v += (data[position] & 0x7F) << (i++ * 7); } while ((data[position++] & 0x80) == 0); return v; } private void write(final int index) { int i = index; boolean last; do { last = i < 0x80; data[position++] = (byte) ((i & 0x7F) | (last ? 0x80 : 0)); i /= 0x80; } while (!last); } private void write(final LineRange r) { write(r.getStart()); write(r.getEnd() - r.getStart()); } /** * Reads the current value at the cursor and compares it. * * @param other * the line range to compare with * * @return {@code true} if the read value is equal to the specified range */ public boolean compare(final LineRange other) { int s = read(); int d = read(); return other.getStart() == s && other.getEnd() == s + d; } /** * Skips forward and gets the pointer to N-th element. * * @param n * number of elements to skip * * @return this cursor */ @CanIgnoreReturnValue private Cursor skip(final int n) { int i = n; for (; i > 0; i--) { read(); read(); } return this; } /** * Counts the # of elements from the current cursor position to the end. * * @return number of elements */ private int count() { int n = 0; while (position < len) { read(); read(); n++; } return n; } @Override public int nextIndex() { throw new UnsupportedOperationException("nextIndex is not supported"); } @Override public int previousIndex() { throw new UnsupportedOperationException("previousIndex is not supported"); } public Cursor copy() { return new Cursor(position); } private void adjust(final int diff) { ensure(len + diff); if (diff > 0) { System.arraycopy(data, position, data, position + diff, len - position); } else { System.arraycopy(data, position - diff, data, position, len - position + diff); } len += diff; } /** * Rewrites the value at the current cursor position. * * @param other * the line range to rewrite * * @return the changed line range */ public LineRange rewrite(final LineRange other) { Cursor c = copy(); LineRange old = c.next(); int oldSize = c.position - position; int newSize = sizeOf(other); adjust(newSize - oldSize); write(other); return old; } @Override public void set(final LineRange v) { rewrite(v); } /** * Inserts the value at the current cursor position. */ @Override public void add(final LineRange v) { int newSize = sizeOf(v); adjust(newSize); write(v); } /** * Removes the current value at the cursor position. * * @return the deleted element */ public LineRange delete() { Cursor c = copy(); LineRange old = c.next(); adjust(position - c.position); return old; } private int sizeOf(final LineRange v) { return sizeOf(v.getStart()) + sizeOf(v.getEnd() - v.getStart()); } /** * Computes the number of bytes that the value 'index' would occupy in its encoded form. * * @param index * the index to check * * @return the number of bytes */ private int sizeOf(final int index) { int i = index; int n = 0; do { i /= 0x80; n++; } while (i > 0); return n; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy