net.sf.eBusx.time.EInterval Maven / Gradle / Ivy
//
// Copyright 2021 Charles W. Rapp
//
// 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 net.sf.eBusx.time;
import java.time.Instant;
import java.util.Objects;
import net.sf.eBus.messages.EField;
import net.sf.eBus.util.Validator;
/**
* Implements a time interval from one {@link Instant} to
* another. {@code EInterval} guarantees that begin time is ≤
* end time. Each end point may be separately configured to be
* inclusive or exclusive. If begin time equals end time then
* both ends must be marked as inclusive.
*
* {@code EInterval} extends {@link EField} so that it can be
* used in an eBus message. This means that
* {@link EInterval.Builder} must be used to create an
* {@code EInterval} instance.
*
*
* {@code EInterval} is immutable and thread-safe.
*
* Allen's Interval Algebra
*
* Allen's temporal reasoning calculus is based on 13 base
* relations. Since 12 of these relations are simply inverses of
* each other (for example the inverse of x precedes y
* is y is preceded by x) only 7 relations are
* implemented in {@code EInterval} which are:
*
*
* -
* :
* interval 1 precedes interval 2 if interval 1 end time <
* interval 2 begin time.
*
* -
* : interval 1
* meets interval 2 if if interval 1 end time equals interval
* 2 begin time.
*
* -
* :
* interval 1 overlaps interval 2 if interval 1 begin time
* < interval 2 begin time and interval 1 end time <
* interval 2 end time.
*
* -
* :
* interval 1 contains interval 2 if interval 1 begin time
* < interval 2 begin time and interval 1 end time >
* interval 2 end time.
*
* -
* : interval 1
* starts interval 2 if interval 1 begin time equals interval
* 2 begin time and interval 1 end time < interval 2 end
* time.
*
* -
* :
* interval 1 finishes interval 2 if interval 1 begin time
* > interval 2 begin time and interval 1 end time equals
* interval 2 end time.
*
* -
* : interval 1
* begin and end times equal interval 2 begin and end times.
*
*
*
* Note: interval algebra methods below are
* based solely on the end point times and do not take
* clusivity into account.
*
*
* @author Charles W. Rapp
*/
public final class EInterval
extends EField
{
//---------------------------------------------------------------
// Member enums.
//
/**
* Specifies whether an end point is inclusive or exclusive.
*/
public enum Clusivity
{
/**
* The end point time is included in the interval.
*/
INCLUSIVE,
/**
* The end point time is excluded from the interval.
*/
EXCLUSIVE
} // end of enum Clusivity
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Java serialization identifier.
*/
private static final long serialVersionUID = 0x00050700l;
//-----------------------------------------------------------
// Statics.
//
//-----------------------------------------------------------
// Locals.
//
/**
* Interval begin time.
*/
public final Instant beginTime;
/**
* Interval begin time clusivity.
*/
public final Clusivity beginClusivity;
/**
* Interval end time.
*/
public final Instant endTime;
/**
* Interval end time clusivity.
*/
public final Clusivity endClusivity;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new interval instance based on the builder
* configuration. {@code Builder} guarantees that the
* interval settings are correct and consistent.
* @param builder contains interval settings.
*/
private EInterval(final Builder builder)
{
super (builder);
this.beginTime = builder.mBeginTime;
this.beginClusivity = builder.mBeginClusivity;
this.endTime = builder.mEndTime;
this.endClusivity = builder.mEndClusivity;
} // end of EInterval()
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns textual representation of end point times and
* clusivities. Uses default {@code Instant} time format.
* @return interval as text.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
return (
retval.append(beginClusivity == Clusivity.INCLUSIVE ?
'[' :
'(')
.append(beginTime)
.append(", ")
.append(endTime)
.append(endClusivity == Clusivity.INCLUSIVE ?
']' :
')')
.toString());
} // end of toString()
/**
* :
* Returns {@code true} if:
*
* -
* {@code o} is the same {@code EInterval} instance as
* {@code this} interval or
*
* -
* {@code o} is a non-{@code null EInterval} instance
* with the same end points and clusivities as
* {@code this EInterval} instance.
*
*
* Otherwise returns {@code false}.
* @param o comparison object.
* @return {@code true} if {@code o} is a
* non-{@code null EInterval} instance whose end points are
* equal and have the same clusivities.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof EInterval)
{
final EInterval interval = (EInterval) o;
retcode =
(beginTime.equals(interval.beginTime) &&
beginClusivity == interval.beginClusivity &&
endTime.equals(interval.endTime) &&
endClusivity == interval.endClusivity);
}
return (retcode);
} // end of equals(Object)
/**
* Returns hash code based on end point times and
* clusivities.
* @return hash code based on end point times and
* clusivities.
*/
@Override
public int hashCode()
{
return (Objects.hash(beginTime,
beginClusivity,
endTime,
endClusivity));
} // end of hashCode()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get Methods.
//
/**
* :
* Returns {@code true} if {@code this EInterval} end time is
* < {@code interval} begin time.
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if {@code this EInterval} precedes
* {@code interval} and {@code false} otherwise.
*/
public boolean precedes(final EInterval interval)
{
return (endTime.compareTo(interval.beginTime) < 0);
} // end of precedes(EInterval)
/**
* :
* Returns {@code true} if {@code this EInterval} end time
* equals {@code interval} begin time.
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if {@code this EInterval} meets
* {@code interval} and {@code false} otherwise.
*/
public boolean meets(final EInterval interval)
{
return (endTime.equals(interval.beginTime));
} // end of meets(EInterval)
/**
* :
* Returns {@code true} if:
*
* -
* {@code this EInterval} end time is >
* {@code interval}'s begin time,
*
* -
* {@code this EInterval} end time is <
* {@code interval}'s end time, and
*
* -
* {@code this EInterval} begin time is <
* {@code interval}'s begin time.
*
*
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if {@code this EInterval} overlaps
* {@code interval} and {@code false} otherwise.
*/
public boolean overlaps(final EInterval interval)
{
return (endTime.compareTo(interval.beginTime) > 0 &&
endTime.compareTo(interval.endTime) < 0 &&
beginTime.compareTo(interval.beginTime) < 0);
} // end of overlaps(EInterval)
/**
* :
* Returns {@code true} if:
*
* -
* {@code this EInterval} begin time <
* {@code interval} begin time and
*
* -
* {@code this EInterval} end time > {@code interval}
* end time.
*
*
* In short, this interval contains the given argument
* interval.
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if this interval begins before
* {@code interval} and ends after.
*/
public boolean contains(final EInterval interval)
{
return (beginTime.compareTo(interval.beginTime) < 0 &&
endTime.compareTo(interval.endTime) > 0);
} // end of contains(EInterval)
/**
* :
* Returns {@code true} if this interval starts at the same
* time as {@code interval} but ends before. That is:
*
* -
* {@code this EInterval} begin time equals
* {@code interval} begin time and
*
* -
* {@code this EInterval} end time < {@code interval}
* end time.
*
*
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if this interval starts at the same
* time as {@code interval} but ends before.
*/
public boolean starts(final EInterval interval)
{
return (beginTime.equals(interval.beginTime) &&
endTime.compareTo(interval.endTime) < 0);
} // end of starts(EInterval)
/**
* :
* Returns {@code true} if this interval starts after
* {@code intervaL} buts ends at the same time. That is:
*
* -
* {@code this EInterval} begin time >
* {@code interval} begin time and
*
* -
* {@code this EInterval} end time equals
* {@code interval} end time.
*
*
*
* Note that this comparison is based only on time and does
* not take clusivity into account.
*
* @param interval comparison time interval.
* @return {@code true} if this interval starts after
* {@code intervaL} buts ends at the same time.
*/
public boolean finishes(final EInterval interval)
{
return (beginTime.compareTo(interval.beginTime) > 0 &&
endTime.equals(interval.endTime));
} // end of finishes(EInterval)
//
// end of Get Methods.
//-----------------------------------------------------------
/**
* Returns a new {@code EInterval} builder instance which is
* used to create an {@code EInterval} object. It is
* recommended that a new builder be used when creating an
* {@code EInterval} instance and not re-use a {@code Builder}
* to create multiple intervals.
* @return interval builder instance.
*/
public static Builder builder()
{
return (new Builder());
} // end of builder()
//---------------------------------------------------------------
// Inner classes.
//
/**
* {@code EInterval} instances may be created only by using
* a {@code Builder} instance. A {@code Builder} instance is
* obtained by calling {@link #builder()} which returns a
* newly instantiated {@code Builder} instance. It is
* recommended that a new {@code Builder} instance be used to
* create an interval rather than re-using a builder instance
* to create multiple intervals.
*/
public static final class Builder
extends EField.Builder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Constants.
//
private static final String BEGIN_TIME = "beginTime";
//-------------------------------------------------------
// Locals.
//
private Instant mBeginTime;
private Clusivity mBeginClusivity;
private Instant mEndTime;
private Clusivity mEndClusivity;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Constructor is private because a Builder instance
* may only be created by calling {@link #builder()}.
*/
private Builder()
{
super (EInterval.class);
} // end of Builder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Abstract Builder Overrides.
//
/**
* Checks if:
*
* -
* the begin and end times and clusivity are set and
*
* -
* begin time is either < or ≤ end time
* depending on clusivity settings.
*
* -
* if begin time equals end time then both times must
* be inclusive.
*
*
* @param problems add each detected problem to this
* validator.
* @return {@code problems}.
*/
@Override
protected Validator validate(final Validator problems)
{
return (problems.requireNotNull(mBeginTime, BEGIN_TIME)
.requireNotNull(mEndTime, "endTime")
.requireTrue(mBeginTime.compareTo(mEndTime) <= 0,
BEGIN_TIME,
"beginTime > endTime")
.requireTrue(
(mBeginTime.equals(mEndTime) &&
(mBeginClusivity == Clusivity.EXCLUSIVE &&
mEndClusivity == Clusivity.EXCLUSIVE)),
BEGIN_TIME,
"beginTime == endTime and both ends are not inclusive"));
} // end of validate(Validator)
/**
* Returns a new interval instance based on this
* builder's settings.
* @return new interval instance.
*/
@Override
protected EInterval buildImpl()
{
return (new EInterval(this));
} // end of buildImpl()
//
// end of Abstract Builder Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets interval begin time. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param beginTime interval begin time.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code beginTime} is {@code null}.
*/
public Builder beginTime(final Instant beginTime)
{
mBeginTime =
Objects.requireNonNull(
beginTime, "beginTime is null");
return (this);
} // end of beginTime(Instant)
/**
* Sets interval begin time clusivity. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param clusivity interval begin time clusivity.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code clusivity} is {@code null}.
*/
public Builder beginClusivity(final Clusivity clusivity)
{
mBeginClusivity =
Objects.requireNonNull(
clusivity, "beginClusivity is null");
return (this);
} // end of beginClusivity(Clusivity)
/**
* Sets the interval end time. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param endTime interval end time.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code endTime} is {@code null}.
*/
public Builder endTime(final Instant endTime)
{
mEndTime =
Objects.requireNonNull(
endTime, "endTime is null");
return (this);
} // end of endTime(Instant)
/**
* Sets interval end time clusivity. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param clusivity interval begin time clusivity.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code clusivity} is {@code null}.
*/
public Builder endClusivity(final Clusivity clusivity)
{
mEndClusivity =
Objects.requireNonNull(
clusivity, "endClusivity is null");
return (this);
} // end of endClusivity(final Clusivity clusivity)
//
// end of Set Methods.
//-------------------------------------------------------
} // end of class Builder
} // end of class EInterval