diff --git a/docs/Interval.md b/docs/Interval.md index 8f3cf27..974476c 100644 --- a/docs/Interval.md +++ b/docs/Interval.md @@ -54,7 +54,6 @@ new Interval<>(1L, 10L).contains(10.0) == true new Interval<>(Long.class, 1L, 2L, false, true).contains(2) == false; ``` - The `compareTo` method validates, if an interval is smaller (`< 0`), equal (`== 0`), or larger (`> 0`) than another interval. The method allows to use a different types interval to compare with, e.g., @@ -67,4 +66,61 @@ new Interval<>(1L, 5L).compareTo(new Interval<>(1, 6)) < 0; // i.e., [1, 5 // the specified type, specifies the boundaries new Interval<>(Double.class, 1.0, 5.0, true, true).compareTo(new Interval<>(Long.class, 1L, 5L, true, true)) > 0; // i.e., (1.0, 5.0) < (1, 5) -``` \ No newline at end of file +``` + +## Extending Intervals... and what to consider + +The provided `Interval` implementation implements the `IInterval` interface, which is needed for all other interval-based +structures in this library (e.g., `IntervalTree`). Nevertheless, when working with interval data, it is often needed to have +more than just the interval itself, i.e., an identifier or more complex additional data may be associated with an interval. +This additional data may also affect the similarity of intervals, i.e., let's assume we have the following data (using JSON): + +``` +[ + { start: 5, end: 10, id: 2 }, + { start: 5, end: 10, id: 3 }, + { start: 5, end: 10, id: 2 } +] +``` + +The JSON shows three intervals, from which two are equal (`{ start: 5, end: 10, id: 2 }`). The third interval +(`{ start: 5, end: 10, id: 3 }`) is unequal to the others (based on the associated data). The default implementation +`Interval` would not recognize the difference and assume all of the three intervals are equal. To modify the handling, +it is recommended to `extend` the `Interval` implementation (and just add the new comparision). + +```java +public class IdInterval, T extends Number & Comparable> extends Interval { + private final I id; + + /* + * We removed the code for constructors and getters/setters, we also + * did not add the compareId method, which just compares the actual + * identifiers (using the compareTo method of these). + */ + + @Override + public int compareTo(final IInterval i) { + final int cmp = super.compareTo(i); + + if (cmp == 0) { + + // the intervals are equal, so we must use the identifiers + if (i instanceof IdInterval) { + return compareId(IdInterval.class.cast(i)); + } + // we don't have any identifiers (the instance is of a different type) + else { + return getClass().getName().compareTo(i.getClass().getName()); + } + } else { + return cmp; + } + } +} +``` + +In this implementation, the most important line is `super.compareTo(i)`. You should always use the `super` implementation, +and only be more general regarding the comparision, if the `super.compareTo` result is `0`. Otherwise, the intervals are +already different and thus, the instances can never be equal. One may argue that the comparison should be based on the identifiers. +This may be correct within specific use-cases, but be aware, that such implementations can not utilize, e.g., interval-based +index structures like the `IntervalTree`. \ No newline at end of file diff --git a/src/com/brein/time/timeintervals/intervals/IdInterval.java b/src/com/brein/time/timeintervals/intervals/IdInterval.java index 8cb772c..e7df629 100644 --- a/src/com/brein/time/timeintervals/intervals/IdInterval.java +++ b/src/com/brein/time/timeintervals/intervals/IdInterval.java @@ -1,34 +1,32 @@ package com.brein.time.timeintervals.intervals; -import java.util.Objects; - public class IdInterval, T extends Number & Comparable> extends Interval { private final I id; public IdInterval(final I id, final Long start, final Long end) { - //noinspection unchecked - this(id, Long.class, (T) start, (T) end, false, false); + super(start, end); + this.id = id; } public IdInterval(final I id, final Integer start, final Integer end) { - //noinspection unchecked - this(id, Integer.class, (T) start, (T) end, false, false); + super(start, end); + this.id = id; } public IdInterval(final I id, final Double start, final Double end) { - //noinspection unchecked - this(id, Double.class, (T) start, (T) end, false, false); + super(start, end); + this.id = id; } public IdInterval(final I id, - final Class clazz, + final Class clazz, final T start, final T end) { this(id, clazz, start, end, false, false); } public IdInterval(final I id, - final Class clazz, + final Class clazz, final T start, final T end, final boolean openStart, @@ -41,8 +39,19 @@ public I getId() { return id; } - public boolean idEquals(final IdInterval i) { - return i != null && Objects.equals(this.id, i.id); + public int compareId(final IdInterval iId) { + if (this.id == null && iId == null) { + return 0; + } else if (this.id == null) { + return -1; + } else if (iId == null) { + return 1; + } else if (this.id.getClass().isInstance(iId.id)) { + //noinspection unchecked + return this.id.compareTo((I) iId.id); + } else { + return this.id.toString().compareTo(iId.id.toString()); + } } @Override @@ -51,47 +60,22 @@ public IdInterval clone() throws CloneNotSupportedException { } @Override - @SuppressWarnings("SimplifiableIfStatement") - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } else if (obj == null) { - return false; - } else if (obj instanceof IdInterval) { - final IdInterval iId = IdInterval.class.cast(obj); - return Objects.equals(this.id, iId.id) && super.equals(iId); - } else if (obj instanceof IInterval) { - return super.equals(obj); - } else { - return false; - } - } - - @Override - @SuppressWarnings({"unchecked", "NullableProblems"}) + @SuppressWarnings("NullableProblems") public int compareTo(final IInterval i) { - if (i instanceof IdInterval) { - final IdInterval iId = IdInterval.class.cast(i); - final int cmp = super.compareTo(iId); - - if (cmp != 0 || Objects.equals(this.id, iId.id)) { - return cmp; - } else if (this.id.getClass().isInstance(iId.id)) { - return this.id.compareTo((I) iId.id); - } else { - return this.id.toString().compareTo(iId.id.toString()); - } - } else { - final int cmp = super.compareTo(i); + final int cmp = super.compareTo(i); - // if they are equal they cannot be, because there is no identifier, so we can only be less or more - if (cmp == 0) { + if (cmp == 0) { - // we don't have any empty identifier, thus we compare the class-names - return this.getClass().getName().compareTo(i.getClass().getName()); - } else { - return cmp; + // the intervals are equal, so we must use the identifiers + if (i instanceof IdInterval) { + return compareId(IdInterval.class.cast(i)); } + // we don't have any identifiers (the instance is of a different type) + else { + return getClass().getName().compareTo(i.getClass().getName()); + } + } else { + return cmp; } } diff --git a/src/com/brein/time/timeintervals/intervals/Interval.java b/src/com/brein/time/timeintervals/intervals/Interval.java index cf6d1fa..d0fb336 100644 --- a/src/com/brein/time/timeintervals/intervals/Interval.java +++ b/src/com/brein/time/timeintervals/intervals/Interval.java @@ -278,8 +278,7 @@ public boolean equals(final Object obj) { } else if (obj == null) { return false; } else if (obj instanceof Interval) { - @SuppressWarnings("unchecked") - final Interval i = Interval.class.cast(obj); + final Interval i = Interval.class.cast(obj); return compareTo(i) == 0; } else { return false;