java/de/jflex/ucd_generator/ucd/CodepointRangeSet.java (141 lines of code) (raw):

/* * Copyright (C) 2009-2013 Steve Rowe <sarowe@gmail.com> * Copyright (C) 2019-2020 Google, LLC. * * SPDX-License-Identifier: BSD-3-Clause */ package de.jflex.ucd_generator.ucd; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import de.jflex.ucd.CodepointRange; import de.jflex.ucd.NamedCodepointRange; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.TreeSet; @AutoValue public abstract class CodepointRangeSet { public abstract ImmutableList<CodepointRange> ranges(); public static Builder builder() { return new AutoValue_CodepointRangeSet.Builder(); } @AutoValue.Builder public abstract static class Builder { private final TreeSet<MutableCodepointRange> mRanges = new TreeSet<>(MutableCodepointRange.COMPARATOR_START_POINT); abstract ImmutableList.Builder<CodepointRange> rangesBuilder(); public Builder addAllImmutable(Collection<CodepointRange> ranges) { if (ranges == null) { return this; } for (CodepointRange range : ranges) { add(range); } return this; } public Builder addAll(Collection<MutableCodepointRange> ranges) { if (ranges == null) { return this; } for (MutableCodepointRange range : ranges) { add(range); } return this; } public void add(NamedCodepointRange interval) { add(interval.range()); } public Builder add(CodepointRange range) { return add(MutableCodepointRange.create(range)); } public Builder add(MutableCodepointRange range) { mRanges.add(range); return this; } public Builder substractAll(Collection<CodepointRange> substractingRanges) { int end = mRanges.last().end; for (CodepointRange substractingRange : substractingRanges) { if (substractingRange.start() > end) { return this; } substract(substractingRange); } return this; } /** * Removes all values in the {@link CodepointRangeSet} that are within {@code * substractingRange}. * * <p>This method assumes intervals are ordered and non-overlapping. */ public Builder substract(CodepointRange substractingRange) { if (mRanges.isEmpty()) { return this; } List<MutableCodepointRange> intersection = intersection(substractingRange); if (intersection.isEmpty()) { return this; } MutableCodepointRange first = intersection.get(0); sub(first, substractingRange); MutableCodepointRange last = intersection.get(intersection.size() - 1); if (!last.equals(first)) { sub(last, substractingRange); } if (intersection.size() > 2) { mRanges.removeAll(intersection.subList(1, intersection.size() - 1)); } return this; } /** Removes sub from range and updates mRanges. */ private void sub(MutableCodepointRange range, CodepointRange sub) { if (sub.start() <= range.start && sub.end() >= range.end) { mRanges.remove(range); } else if (range.start < sub.start() && sub.end() < range.end) { // sub is a strict subset mRanges.add(MutableCodepointRange.create(sub.end() + 1, range.end)); range.end = sub.start() - 1; } else if (sub.start() <= range.start) { range.start = sub.end() + 1; } else if (range.end <= sub.end()) { range.end = sub.start() - 1; } else { throw new IllegalStateException( String.format("Cannot remove sub=%s from range=%s", sub, range)); } } /** Returns all intervals that intersect with intersecting. */ @VisibleForTesting List<MutableCodepointRange> intersection(CodepointRange intersecting) { List<MutableCodepointRange> intersection = new ArrayList<>(); MutableCodepointRange start = MutableCodepointRange.createPoint(intersecting.start()); MutableCodepointRange end = MutableCodepointRange.createPoint(intersecting.end() + 1); // All intervals that start strictly after the intersecting (but not after its end) SortedSet<MutableCodepointRange> subset = mRanges.subSet(start, end); // Also consider the last interval which was starting before try { SortedSet<MutableCodepointRange> prevRanges = subset.isEmpty() ? mRanges.headSet(end) : mRanges.headSet(subset.first()); MutableCodepointRange prevRange = prevRanges.last(); if (prevRange.start < intersecting.start() && intersecting.start() <= prevRange.end) { intersection.add(0, prevRange); } } catch (NoSuchElementException e) { // lastRange remains null } intersection.addAll(subset); return intersection; } public CodepointRangeSet build() { Preconditions.checkState(!mRanges.isEmpty(), "Cannot create an empty set"); internalAddRanges(); return internalBuild(); } private void internalAddRanges() { MutableCodepointRange lastRange = null; for (MutableCodepointRange r : mRanges) { if (lastRange == null) { lastRange = r; continue; } if (lastRange.end + 1 == r.start) { lastRange.end = r.end; } else { internalAddRange(lastRange); lastRange = r; } } internalAddRange(lastRange); } private void internalAddRange(MutableCodepointRange range) { rangesBuilder().add(range.toImmutableRange()); } abstract CodepointRangeSet internalBuild(); } }