org.conqat.lib.commons.collections.CompactLines Maven / Gradle / Ivy
Show all versions of teamscale-lib-commons Show documentation
/*
* Copyright (c) CQSE GmbH
*
* 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 org.conqat.lib.commons.collections;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.conqat.lib.commons.filesystem.FileSystemUtils;
import org.conqat.lib.commons.region.LineBasedRegion;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.test.IndexValueClass;
/**
* A compact, serializable representation of line numbers using a BitSet. This class is designed to
* efficiently store and manipulate sets of line numbers, which is particularly useful for tracking
* coverage information, regions of a text, or any scenario where line-based data needs to be
* compactly managed.
*
* Instances of this class can be created empty, from a collection of integers, or from a string
* representation of line number ranges. It supports basic set operations such as addition, removal,
* intersection, and union, as well as specialized operations like checking if any line number
* within a range or specific region is present.
*
* This class also implements {@link Iterable}, allowing for easy iteration over all stored line
* numbers.
*
* @see BitSet
*/
@IndexValueClass(containedInBackup = true)
public class CompactLines implements Serializable, Iterable {
private static final long serialVersionUID = 1L;
private BitSet bitSet = new BitSet();
public CompactLines() {
}
public CompactLines(Iterable lines) {
for (Integer line : lines) {
bitSet.set(line);
}
}
public CompactLines(int... lines) {
for (Integer line : lines) {
bitSet.set(line);
}
}
/** Returns the number of line numbers in this set. */
public int size() {
return bitSet.cardinality();
}
/**
* Checks if this set of line numbers is empty.
*
* @return {@code true} if there are no line numbers in this set, {@code false} otherwise.
*/
public boolean isEmpty() {
return bitSet.isEmpty();
}
/**
* Adds all line numbers from another {@code CompactLines} instance to this one.
*/
public void addAll(CompactLines lines) {
bitSet.or(lines.bitSet);
}
/**
* Checks if a specific line number is present in this set.
*
* @param line
* The line number (1-based)
* @return {@code true} if the line number is present, {@code false} otherwise.
*/
public boolean contains(int line) {
return bitSet.get(line);
}
/**
* Checks if any line number within a specified range is present in this set.
*
* @param start
* the start of the range (inclusive, 1-based).
* @param end
* the end of the range (inclusive, 1-based).
* @return {@code true} if any line number within the range is present, {@code false} otherwise.
*/
public boolean containsAny(int start, int end) {
int nextSetBit = bitSet.nextSetBit(start);
return nextSetBit != -1 && nextSetBit <= end;
}
/**
* Checks if any line number within a specified range is present in this set.
*
* @see #containsAny(int, int)
*/
public boolean containsAny(LineBasedRegion region) {
return containsAny(region.getStart(), region.getEnd());
}
/**
* Checks if this set contains all the line numbers specified in an iterable collection.
*
* @return {@code true} if every line number in the collection is contained in this set,
* {@code false} otherwise.
*/
public boolean containsAll(Iterable lines) {
for (Integer line : lines) {
if (!bitSet.get(line)) {
return false;
}
}
return true;
}
/**
* Adds a specific line number to this set.
*
* @param line
* The line number (1-based)
*/
public void add(int line) {
bitSet.set(line);
}
/**
* Adds a range of line numbers to this set.
*
* @param startLine
* the starting line number of the range to add (inclusive, 1-based)
* @param endLine
* the ending line number of the range to add (inclusive, 1-based)
*/
public void addRange(int startLine, int endLine) {
bitSet.set(startLine, endLine + 1);
}
/** Removes a specific line number from this set. */
public void remove(int line) {
bitSet.clear(line);
}
/**
* Removes all line numbers that are present in another {@code CompactLines} instance from this one.
*/
public void removeAll(CompactLines lines) {
bitSet.andNot(lines.bitSet);
}
/** Clears all line numbers from this set. */
public void clear() {
bitSet.clear();
}
/**
* Retains only the line numbers that are present in both this and another {@code CompactLines}
* instance. This basically builds the intersection set between both.
*/
public void retainAll(CompactLines lines) {
bitSet.and(lines.bitSet);
}
/**
* Creates a new {@link CompactLines} object with the intersection of this and the other lines.
*/
public CompactLines intersection(CompactLines other) {
CompactLines intersection = new CompactLines(this);
intersection.retainAll(other);
return intersection;
}
/**
* Checks if there is any overlap between the line numbers in this and another {@code CompactLines}
* instance.
*
* @return {@code true} if there is at least one common line number, {@code false} otherwise.
*/
public boolean intersects(CompactLines lines) {
return bitSet.intersects(lines.bitSet);
}
@Override
public String toString() {
return StringUtils.concat(this, ",");
}
/**
* Gets the highest line number contained in this set or empty if there are no line numbers
* contained.
*/
public Optional getHighestLineNumber() {
if (bitSet.isEmpty()) {
return Optional.empty();
}
return Optional.of(bitSet.previousSetBit(bitSet.length() - 1));
}
private void readObject(ObjectInputStream inputStream) throws IOException {
byte[] bytes = FileSystemUtils.readStreamBinary(inputStream);
bitSet = BitSet.valueOf(bytes);
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
byte[] bytes = bitSet.toByteArray();
outputStream.write(bytes);
}
@NonNull
@Override
public Iterator iterator() {
return new Iterator<>() {
private int currentIndex = -1;
@Override
public boolean hasNext() {
int nextIndex = bitSet.nextSetBit(currentIndex + 1);
return nextIndex != -1;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
currentIndex = bitSet.nextSetBit(currentIndex + 1);
return currentIndex;
}
};
}
}