blob: db6c3aa021b4d36490e21477e1ef5fe1c82c4985 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 accord.primitives;
import accord.api.Key;
import accord.api.RoutingKey;
import accord.utils.Invariants;
import accord.utils.SortedArrays;
import accord.utils.SortedArrays.Search;
import java.util.Objects;
import static accord.utils.SortedArrays.Search.CEIL;
import static accord.utils.SortedArrays.Search.FAST;
/**
* A range of keys
*/
public abstract class Range implements Comparable<RoutableKey>, Unseekable, Seekable
{
public static class EndInclusive extends Range
{
public EndInclusive(RoutingKey start, RoutingKey end)
{
super(start, end);
}
@Override
public int compareTo(RoutableKey key)
{
if (key.compareTo(start()) <= 0)
return 1;
if (key.compareTo(end()) > 0)
return -1;
return 0;
}
@Override
public boolean startInclusive()
{
return false;
}
@Override
public boolean endInclusive()
{
return true;
}
@Override
public Range newRange(RoutingKey start, RoutingKey end)
{
return new EndInclusive(start, end);
}
@Override
public String toString()
{
return "Range(" + start() + ", " + end() + ']';
}
}
public static class StartInclusive extends Range
{
public StartInclusive(RoutingKey start, RoutingKey end)
{
super(start, end);
}
@Override
public int compareTo(RoutableKey key)
{
if (key.compareTo(start()) < 0)
return 1;
if (key.compareTo(end()) >= 0)
return -1;
return 0;
}
@Override
public boolean startInclusive()
{
return true;
}
@Override
public boolean endInclusive()
{
return false;
}
@Override
public Range newRange(RoutingKey start, RoutingKey end)
{
return new StartInclusive(start, end);
}
@Override
public String toString()
{
return "Range[" + start() + ", " + end() + ')';
}
}
public static Range range(RoutingKey start, RoutingKey end, boolean startInclusive, boolean endInclusive)
{
return new Range(start, end) {
@Override
public boolean startInclusive()
{
return startInclusive;
}
@Override
public boolean endInclusive()
{
return endInclusive;
}
@Override
public Range newRange(RoutingKey start, RoutingKey end)
{
throw new UnsupportedOperationException("subRange");
}
@Override
public int compareTo(RoutableKey key)
{
if (startInclusive)
{
if (key.compareTo(start()) < 0)
return 1;
}
else
{
if (key.compareTo(start()) <= 0)
return 1;
}
if (endInclusive)
{
if (key.compareTo(end()) > 0)
return -1;
}
else
{
if (key.compareTo(end()) >= 0)
return -1;
}
return 0;
}
};
}
private final RoutingKey start;
private final RoutingKey end;
private Range(RoutingKey start, RoutingKey end)
{
if (start.compareTo(end) >= 0)
throw new IllegalArgumentException(start + " >= " + end);
if (startInclusive() == endInclusive())
throw new IllegalStateException("Range must have one side inclusive, and the other exclusive. Range of different types should not be mixed.");
this.start = start;
this.end = end;
}
public final RoutingKey start()
{
return start;
}
public final RoutingKey end()
{
return end;
}
@Override
public final Domain domain() { return Domain.Range; }
public abstract boolean startInclusive();
public abstract boolean endInclusive();
public abstract Range newRange(RoutingKey start, RoutingKey end);
@Override
public Key asKey()
{
throw new UnsupportedOperationException();
}
@Override
public Range asRange()
{
return this;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Range that = (Range) o;
return Objects.equals(start, that.start) && Objects.equals(end, that.end);
}
@Override
public int hashCode()
{
return start.hashCode() * 31 + end.hashCode();
}
/**
* Returns a negative integer, zero, or a positive integer as the provided key is greater than, contained by,
* or less than this range.
*/
@Override
public abstract int compareTo(RoutableKey key);
public boolean contains(RoutableKey key)
{
return compareTo(key) == 0;
}
/**
* Returns a negative integer, zero, or a positive integer if both points of the provided range are less than, the
* range intersects this range, or both points are greater than this range
*/
public int compareIntersecting(Range that)
{
if (that.getClass() != this.getClass())
throw new IllegalArgumentException("Cannot mix Range of different types");
if (this.start.compareTo(that.end) >= 0)
return 1;
if (this.end.compareTo(that.start) <= 0)
return -1;
return 0;
}
/**
* Sorts by start then end
*/
public int compare(Range that)
{
if (that.getClass() != this.getClass())
throw new IllegalArgumentException("Cannot mix Range of different types");
int c = this.start.compareTo(that.start);
if (c == 0) c = this.end.compareTo(that.end);
return c;
}
public boolean contains(Range that)
{
return that.start.compareTo(this.start) >= 0 && that.end.compareTo(this.end) <= 0;
}
public Range slice(Range truncateTo)
{
int cs = start.compareTo(truncateTo.start);
int ce = end.compareTo(truncateTo.end);
if (cs >= 0 && ce <= 0) return this;
return newRange(cs >= 0 ? start : truncateTo.start, ce <= 0 ? end : truncateTo.end);
}
public boolean intersects(AbstractKeys<?, ?> keys)
{
return SortedArrays.binarySearch(keys.keys, 0, keys.size(), this, Range::compareTo, FAST) >= 0;
}
/**
* Returns a range covering the overlapping parts of this and the provided range, returns
* null if the ranges do not overlap
*/
public Range intersection(Range that)
{
if (this.compareIntersecting(that) != 0)
return null;
RoutingKey start = this.start.compareTo(that.start) > 0 ? this.start : that.start;
RoutingKey end = this.end.compareTo(that.end) < 0 ? this.end : that.end;
return newRange(start, end);
}
/**
* returns the index of the first key larger than what's covered by this range
*/
public int nextHigherKeyIndex(AbstractKeys<?, ?> keys, int from)
{
int i = SortedArrays.exponentialSearch(keys.keys, from, keys.size(), this, Range::compareTo, Search.FLOOR);
if (i < 0) i = -1 - i;
else i += 1;
return i;
}
/**
* returns the index of the lowest key contained in this range. If the keys object contains no intersecting
* keys, <code>(-(<i>insertion point</i>) - 1)</code> is returned. Where <i>insertion point</i> is where an
* intersecting key would be inserted into the keys array
* @param keys
*/
public int nextCeilKeyIndex(Keys keys, int from)
{
return SortedArrays.exponentialSearch(keys.keys, from, keys.size(), this, Range::compareTo, CEIL);
}
@Override
public RoutingKey someIntersectingRoutingKey(Ranges ranges)
{
if (ranges == null)
return startInclusive() ? start.toUnseekable() : end.toUnseekable();
int i = ranges.indexOf(this);
Range that = ranges.get(i);
if (this.start().compareTo(that.start()) <= 0)
{
if (startInclusive())
return that.start();
if (this.end().compareTo(that.end()) <= 0)
return this.end();
return that.end();
}
else
{
if (startInclusive())
return this.start();
if (that.end().compareTo(this.end()) <= 0)
return that.end();
return this.end();
}
}
public static Range slice(Range bound, Range toSlice)
{
Invariants.checkArgument(bound.compareIntersecting(toSlice) == 0);
if (bound.contains(toSlice))
return toSlice;
return toSlice.newRange(
toSlice.start().compareTo(bound.start()) >= 0 ? toSlice.start() : bound.start(),
toSlice.end().compareTo(bound.end()) <= 0 ? toSlice.end() : bound.end()
);
}
@Override
public Range toUnseekable()
{
return this;
}
}