blob: c7142631f14ef2bd2deae0668b3da4143baeb496 [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 org.apache.ofbiz.service.calendar;
import java.io.Serializable;
import com.ibm.icu.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.TreeSet;
import org.apache.ofbiz.base.util.Debug;
/** A collection of TemporalExpression classes.
* <p>For the most part, these classes are immutable - with the exception
* of the <code>id</code> field. The basic idea is to construct an expression
* tree in memory, and then query it.</p>
*/
@SuppressWarnings("serial")
public class TemporalExpressions implements Serializable {
public static final String module = TemporalExpressions.class.getName();
public static final TemporalExpression NullExpression = new Null();
// Expressions are evaluated from smallest unit of time to largest.
// When unit of time is the same, then they are evaluated from
// least ambiguous to most. Frequency should always be first -
// since it is the most specific. Date range should always be last.
// The idea is to evaluate all other expressions, then check to see
// if the result falls within the date range.
// Difference: adopts the sequence of its include expression
// Intersection: aggregates member expression sequence values
// Substitution: adopts the sequence of its include expression
// Union: adopts the sequence of its first member expression
public static final int SEQUENCE_DATE_RANGE = 800;
public static final int SEQUENCE_DAY_IN_MONTH = 460;
public static final int SEQUENCE_DOM_RANGE = 400;
public static final int SEQUENCE_DOW_RANGE = 450;
public static final int SEQUENCE_FREQ = 100;
public static final int SEQUENCE_HOUR_RANGE = 300;
public static final int SEQUENCE_MINUTE_RANGE = 200;
public static final int SEQUENCE_MONTH_RANGE = 600;
/** A temporal expression that represents a range of dates. */
public static class DateRange extends TemporalExpression {
protected final org.apache.ofbiz.base.util.DateRange range;
public DateRange(Date date) {
this(date, date);
}
public DateRange(Date start, Date end) {
this.range = new org.apache.ofbiz.base.util.DateRange(start, end);
this.sequence = SEQUENCE_DATE_RANGE;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
return this.range.equals(((DateRange) obj).range);
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
return includesDate(cal) ? cal : null;
}
/** Returns the contained <code>org.apache.ofbiz.base.util.DateRange</code>.
* @return The contained <code>org.apache.ofbiz.base.util.DateRange</code>
*/
public org.apache.ofbiz.base.util.DateRange getDateRange() {
return this.range;
}
@Override
public boolean includesDate(Calendar cal) {
return this.range.includesDate(cal.getTime());
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
return this.range.includesDate(cal.getTime());
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
return includesDate(cal) ? cal : null;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.range.start() + ", end = " + this.range.end();
}
}
/** A temporal expression that represents a day in the month. */
public static class DayInMonth extends TemporalExpression {
protected final int dayOfWeek;
protected final int occurrence;
/**
* @param dayOfWeek An integer in the range of <code>Calendar.SUNDAY</code>
* to <code>Calendar.SATURDAY</code>
* @param occurrence An integer in the range of -5 to 5, excluding zero
*/
public DayInMonth(int dayOfWeek, int occurrence) {
if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) {
throw new IllegalArgumentException("Invalid day argument");
}
if (occurrence < -5 || occurrence == 0 || occurrence > 5) {
throw new IllegalArgumentException("Invalid occurrence argument");
}
this.dayOfWeek = dayOfWeek;
this.occurrence = occurrence;
int result = occurrence;
if (result < 0) {
// Make negative values a higher sequence
// Example: Last Monday should come after first Monday
result += 11;
}
this.sequence = SEQUENCE_DAY_IN_MONTH + (result * 10) + dayOfWeek;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
protected Calendar alignDayOfWeek(Calendar cal) {
cal.set(Calendar.DAY_OF_MONTH, 1);
if (this.occurrence > 0) {
while (cal.get(Calendar.DAY_OF_WEEK) != this.dayOfWeek) {
cal.add(Calendar.DAY_OF_MONTH, 1);
}
cal.add(Calendar.DAY_OF_MONTH, (this.occurrence - 1) * 7);
} else {
cal.add(Calendar.MONTH, 1);
cal.add(Calendar.DAY_OF_MONTH, -1);
while (cal.get(Calendar.DAY_OF_WEEK) != this.dayOfWeek) {
cal.add(Calendar.DAY_OF_MONTH, -1);
}
cal.add(Calendar.DAY_OF_MONTH, (this.occurrence + 1) * 7);
}
return cal;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
DayInMonth that = (DayInMonth) obj;
return this.dayOfWeek == that.dayOfWeek && this.occurrence == that.occurrence;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
int month = cal.get(Calendar.MONTH);
Calendar first = alignDayOfWeek((Calendar) cal.clone());
if (first.before(cal)) {
first.set(Calendar.DAY_OF_MONTH, 1);
if (first.get(Calendar.MONTH) == month) {
first.add(Calendar.MONTH, 1);
}
alignDayOfWeek(first);
}
return first;
}
/** Returns the day of week in this expression.
* @return The day of week in this expression
*/
public int getDayOfWeek() {
return this.dayOfWeek;
}
/** Returns the occurrence in this expression.
* @return The occurrence in this expression
*/
public int getOccurrence() {
return this.occurrence;
}
@Override
public boolean includesDate(Calendar cal) {
if (cal.get(Calendar.DAY_OF_WEEK) != this.dayOfWeek) {
return false;
}
int month = cal.get(Calendar.MONTH);
int dom = cal.get(Calendar.DAY_OF_MONTH);
Calendar next = (Calendar) cal.clone();
alignDayOfWeek(next);
return dom == next.get(Calendar.DAY_OF_MONTH) && next.get(Calendar.MONTH) == month;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.DAY_OF_MONTH, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.DAY_OF_MONTH, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
int month = cal.get(Calendar.MONTH);
Calendar next = alignDayOfWeek((Calendar) cal.clone());
if (next.before(cal) || next.equals(cal)) {
next.set(Calendar.DAY_OF_MONTH, 1);
if (next.get(Calendar.MONTH) == month) {
next.add(Calendar.MONTH, 1);
}
alignDayOfWeek(next);
}
return next;
}
@Override
public String toString() {
return super.toString() + ", dayOfWeek = " + this.dayOfWeek + ", occurrence = " + this.occurrence;
}
}
/** A temporal expression that represents a day of month range. */
public static class DayOfMonthRange extends TemporalExpression {
protected final int end;
protected final int start;
public DayOfMonthRange(int dom) {
this(dom, dom);
}
/**
* @param start An integer in the range of 1 to 31
* @param end An integer in the range of 1 to 31
*/
public DayOfMonthRange(int start, int end) {
if (start < 1 || start > end) {
throw new IllegalArgumentException("Invalid start argument");
}
if (end < 1 || end > 31) {
throw new IllegalArgumentException("Invalid end argument");
}
this.sequence = SEQUENCE_DOM_RANGE + start;
this.start = start;
this.end = end;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
DayOfMonthRange that = (DayOfMonthRange) obj;
return this.start == that.start && this.end == that.end;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
while (!includesDate(first)) {
first.add(Calendar.DAY_OF_MONTH, 1);
}
return first;
}
/** Returns the ending day of this range.
* @return The ending day of this range
*/
public int getEndDay() {
return this.end;
}
/** Returns the starting day of this range.
* @return The starting day of this range
*/
public int getStartDay() {
return this.start;
}
@Override
public boolean includesDate(Calendar cal) {
int dom = cal.get(Calendar.DAY_OF_MONTH);
return dom >= this.start && dom <= this.end;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.DAY_OF_MONTH, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.DAY_OF_MONTH, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
next.add(Calendar.DAY_OF_MONTH, 1);
while (!includesDate(next)) {
next.add(Calendar.DAY_OF_MONTH, 1);
}
return next;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", end = " + this.end;
}
}
/** A temporal expression that represents a day of week range. */
public static class DayOfWeekRange extends TemporalExpression {
protected final int end;
protected final int start;
public DayOfWeekRange(int dow) {
this(dow, dow);
}
/**
* @param start An integer in the range of <code>Calendar.SUNDAY</code>
* to <code>Calendar.SATURDAY</code>
* @param end An integer in the range of <code>Calendar.SUNDAY</code>
* to <code>Calendar.SATURDAY</code>
*/
public DayOfWeekRange(int start, int end) {
if (start < Calendar.SUNDAY || start > Calendar.SATURDAY) {
throw new IllegalArgumentException("Invalid start argument");
}
if (end < Calendar.SUNDAY || end > Calendar.SATURDAY) {
throw new IllegalArgumentException("Invalid end argument");
}
this.sequence = SEQUENCE_DOW_RANGE + start;
this.start = start;
this.end = end;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
DayOfWeekRange that = (DayOfWeekRange) obj;
return this.start == that.start && this.end == that.end;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
while (!includesDate(first)) {
first.add(Calendar.DAY_OF_MONTH, 1);
}
return first;
}
/** Returns the ending day of this range.
* @return The ending day of this range
*/
public int getEndDay() {
return this.end;
}
/** Returns the starting day of this range.
* @return The starting day of this range
*/
public int getStartDay() {
return this.start;
}
@Override
public boolean includesDate(Calendar cal) {
int dow = cal.get(Calendar.DAY_OF_WEEK);
if (dow == this.start || dow == this.end) {
return true;
}
Calendar compareCal = (Calendar) cal.clone();
while (compareCal.get(Calendar.DAY_OF_WEEK) != this.start) {
compareCal.add(Calendar.DAY_OF_MONTH, 1);
}
while (compareCal.get(Calendar.DAY_OF_WEEK) != this.end) {
if (compareCal.get(Calendar.DAY_OF_WEEK) == dow) {
return true;
}
compareCal.add(Calendar.DAY_OF_MONTH, 1);
}
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.DAY_OF_MONTH, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.DAY_OF_MONTH, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
if (includesDate(next)) {
if (context.dayBumped) {
context.dayBumped = false;
return next;
}
next.add(Calendar.DAY_OF_MONTH, 1);
}
while (!includesDate(next)) {
next.add(Calendar.DAY_OF_MONTH, 1);
}
if (cal.get(Calendar.MONTH) != next.get(Calendar.MONTH)) {
context.monthBumped = true;
}
return next;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", end = " + this.end;
}
}
/** A temporal expression that represents a difference of two temporal expressions. */
public static class Difference extends TemporalExpression {
protected final TemporalExpression excluded;
protected final TemporalExpression included;
public Difference(TemporalExpression included, TemporalExpression excluded) {
if (included == null) {
throw new IllegalArgumentException("included argument cannot be null");
}
this.included = included;
this.excluded = excluded;
if (containsExpression(this)) {
throw new IllegalArgumentException("recursive expression");
}
this.sequence = included.sequence;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
protected boolean containsExpression(TemporalExpression expression) {
return this.included.containsExpression(expression) || this.excluded.containsExpression(expression);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
Difference that = (Difference) obj;
return this.included.equals(that.included) && this.excluded.equals(that.excluded);
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = this.included.first(cal);
while (first != null && this.excluded.includesDate(first)) {
first = this.included.next(first);
}
return first;
}
/** Returns the excluded expression.
* @return The excluded <code>TemporalExpression</code>
*/
public TemporalExpression getExcluded() {
return this.excluded;
}
/** Returns the included expression.
* @return The included <code>TemporalExpression</code>
*/
public TemporalExpression getIncluded() {
return this.included;
}
@Override
public boolean includesDate(Calendar cal) {
return this.included.includesDate(cal) && !this.excluded.includesDate(cal);
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
return this.included.isSubstitutionCandidate(cal, expressionToTest) && !this.excluded.isSubstitutionCandidate(cal, expressionToTest);
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = this.included.next(cal, context);
while (next != null && this.excluded.includesDate(next)) {
next = this.included.next(next, context);
}
return next;
}
@Override
public String toString() {
return super.toString() + ", included = " + this.included + ", excluded = " + this.excluded;
}
}
/** A temporal expression that represents a frequency. */
public static class Frequency extends TemporalExpression {
protected final int freqCount;
protected final int freqType;
protected final Date start;
/**
* @param start Starting date, defaults to current system time
* @param freqType One of the following integer values: <code>Calendar.SECOND
* Calendar.MINUTE Calendar.HOUR Calendar.DAY_OF_MONTH Calendar.MONTH
* Calendar.YEAR</code>
* @param freqCount A positive integer
*/
public Frequency(Date start, int freqType, int freqCount) {
if (freqType != Calendar.SECOND && freqType != Calendar.MINUTE
&& freqType != Calendar.HOUR && freqType != Calendar.DAY_OF_MONTH
&& freqType != Calendar.MONTH && freqType != Calendar.YEAR) {
throw new IllegalArgumentException("Invalid freqType argument");
}
if (freqCount < 1) {
throw new IllegalArgumentException("freqCount argument must be a positive integer");
}
if (start != null) {
this.start = start;
} else {
this.start = new Date();
}
this.sequence = SEQUENCE_FREQ + freqType;
this.freqType = freqType;
this.freqCount = freqCount;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
Frequency that = (Frequency) obj;
return this.start.equals(that.start) && this.freqType == that.freqType && this.freqCount == that.freqCount;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = prepareCal(cal);
while (first.before(cal)) {
first.add(this.freqType, this.freqCount);
}
return first;
}
/** Returns the frequency count of this expression.
* @return The frequency count of this expression
*/
public int getFreqCount() {
return this.freqCount;
}
/** Returns the frequency type of this expression.
* @return The frequency type of this expression
*/
public int getFreqType() {
return this.freqType;
}
/** Returns the start date of this expression.
* @return The start date of this expression
*/
public Date getStartDate() {
return (Date) this.start.clone();
}
@Override
public boolean includesDate(Calendar cal) {
Calendar next = first(cal);
return next.equals(cal);
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(this.freqType, -this.freqCount);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(this.freqType, -this.freqCount);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = first(cal);
if (next.equals(cal)) {
next.add(this.freqType, this.freqCount);
}
return next;
}
protected Calendar prepareCal(Calendar cal) {
// Performs a "sane" skip forward in time - avoids time consuming loops
// like incrementing every second from Jan 1 2000 until today
Calendar skip = (Calendar) cal.clone();
skip.setTime(this.start);
long deltaMillis = cal.getTimeInMillis() - this.start.getTime();
if (deltaMillis < 1000) {
return skip;
}
long divisor = deltaMillis;
if (this.freqType == Calendar.DAY_OF_MONTH) {
divisor = 86400000;
} else if (this.freqType == Calendar.HOUR) {
divisor = 3600000;
} else if (this.freqType == Calendar.MINUTE) {
divisor = 60000;
} else if (this.freqType == Calendar.SECOND) {
divisor = 1000;
} else {
return skip;
}
float units = deltaMillis / divisor;
units = (units / this.freqCount) * this.freqCount;
skip.add(this.freqType, (int)units);
while (skip.after(cal)) {
skip.add(this.freqType, -this.freqCount);
}
return skip;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", freqType = " + this.freqType + ", freqCount = " + this.freqCount;
}
}
/** A temporal expression that represents an hour range. */
public static class HourRange extends TemporalExpression {
protected final int end;
protected final int start;
/**
* @param hour An integer in the range of 0 to 23.
*/
public HourRange(int hour) {
this(hour, hour);
}
/**
* @param start An integer in the range of 0 to 23.
* @param end An integer in the range of 0 to 23.
*/
public HourRange(int start, int end) {
if (start < 0 || start > 23) {
throw new IllegalArgumentException("Invalid start argument");
}
if (end < 0 || end > 23) {
throw new IllegalArgumentException("Invalid end argument");
}
this.start = start;
this.end = end;
this.sequence = SEQUENCE_HOUR_RANGE + start;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
HourRange that = (HourRange) obj;
return this.start == that.start && this.end == that.end;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
while (!includesDate(first)) {
first.add(Calendar.HOUR_OF_DAY, 1);
}
return first;
}
/** Returns the ending hour of this range.
* @return The ending hour of this range
*/
public int getEndHour() {
return this.end;
}
public Set<Integer> getHourRangeAsSet() {
Set<Integer> rangeSet = new TreeSet<Integer>();
if (this.start == this.end) {
rangeSet.add(this.start);
} else {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, this.start);
while (cal.get(Calendar.HOUR_OF_DAY) != this.end) {
rangeSet.add(cal.get(Calendar.HOUR_OF_DAY));
cal.add(Calendar.HOUR_OF_DAY, 1);
}
}
return rangeSet;
}
/** Returns the starting hour of this range.
* @return The starting hour of this range
*/
public int getStartHour() {
return this.start;
}
@Override
public boolean includesDate(Calendar cal) {
int hour = cal.get(Calendar.HOUR_OF_DAY);
if (hour == this.start || hour == this.end) {
return true;
}
Calendar compareCal = (Calendar) cal.clone();
compareCal.set(Calendar.HOUR_OF_DAY, this.start);
while (compareCal.get(Calendar.HOUR_OF_DAY) != this.end) {
if (compareCal.get(Calendar.HOUR_OF_DAY) == hour) {
return true;
}
compareCal.add(Calendar.HOUR_OF_DAY, 1);
}
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.HOUR_OF_DAY, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.HOUR_OF_DAY, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
if (includesDate(next)) {
if (context.hourBumped) {
return next;
}
next.add(Calendar.HOUR_OF_DAY, 1);
}
while (!includesDate(next)) {
next.add(Calendar.HOUR_OF_DAY, 1);
}
if (cal.get(Calendar.DAY_OF_MONTH) != next.get(Calendar.DAY_OF_MONTH)) {
context.dayBumped = true;
}
return next;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", end = " + this.end;
}
}
/** A temporal expression that represents a mathematical intersection of all of its
* member expressions. */
public static class Intersection extends TemporalExpression {
protected final Set<TemporalExpression> expressionSet;
public Intersection(Set<TemporalExpression> expressionSet) {
if (expressionSet == null) {
throw new IllegalArgumentException("expressionSet argument cannot be null");
}
this.expressionSet = expressionSet;
if (containsExpression(this)) {
throw new IllegalArgumentException("recursive expression");
}
if (this.expressionSet.size() > 0) {
// Aggregate member expression sequences in a way that will
// ensure the proper evaluation sequence for the entire collection
int result = 0;
TemporalExpression[] exprArray = this.expressionSet.toArray(new TemporalExpression[this.expressionSet.size()]);
for (int i = exprArray.length - 1; i >= 0; i--) {
result *= 10;
result += exprArray[i].sequence;
}
this.sequence = result;
}
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
protected boolean containsExpression(TemporalExpression expression) {
for (TemporalExpression setItem : this.expressionSet) {
if (setItem.containsExpression(expression)) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
return this.expressionSet.equals(((Intersection) obj).expressionSet);
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
for (TemporalExpression expression : this.expressionSet) {
first = expression.first(first);
if (first == null) {
return null;
}
}
if (includesDate(first)) {
return first;
} else {
return null;
}
}
/** Returns the member expression <code>Set</code>. The
* returned set is unmodifiable.
* @return The member expression <code>Set</code>
*/
public Set<TemporalExpression> getExpressionSet() {
return Collections.unmodifiableSet(this.expressionSet);
}
@Override
public boolean includesDate(Calendar cal) {
for (TemporalExpression expression : this.expressionSet) {
if (!expression.includesDate(cal)) {
return false;
}
}
return true;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
for (TemporalExpression expression : this.expressionSet) {
if (!expression.isSubstitutionCandidate(cal, expressionToTest)) {
return false;
}
}
return true;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
for (TemporalExpression expression : this.expressionSet) {
next = expression.next(next, context);
if (next == null) {
return null;
}
}
return next;
}
@Override
public String toString() {
return super.toString() + ", size = " + this.expressionSet.size();
}
}
/** A temporal expression that represents a minute range. */
public static class MinuteRange extends TemporalExpression {
protected final int end;
protected final int start;
/**
* @param minute An integer in the range of 0 to 59.
*/
public MinuteRange(int minute) {
this(minute, minute);
}
/**
* @param start An integer in the range of 0 to 59.
* @param end An integer in the range of 0 to 59.
*/
public MinuteRange(int start, int end) {
if (start < 0 || start > 59) {
throw new IllegalArgumentException("Invalid start argument");
}
if (end < 0 || end > 59) {
throw new IllegalArgumentException("Invalid end argument");
}
this.start = start;
this.end = end;
this.sequence = SEQUENCE_MINUTE_RANGE + start;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
MinuteRange that = (MinuteRange) obj;
return this.start == that.start && this.end == that.end;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
while (!includesDate(first)) {
first.add(Calendar.MINUTE, 1);
}
return first;
}
/** Returns the ending minute of this range.
* @return The ending minute of this range
*/
public int getEndMinute() {
return this.end;
}
public Set<Integer> getMinuteRangeAsSet() {
Set<Integer> rangeSet = new TreeSet<Integer>();
if (this.start == this.end) {
rangeSet.add(this.start);
} else {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, this.start);
while (cal.get(Calendar.HOUR_OF_DAY) != this.end) {
rangeSet.add(cal.get(Calendar.HOUR_OF_DAY));
cal.add(Calendar.HOUR_OF_DAY, 1);
}
}
return rangeSet;
}
/** Returns the starting minute of this range.
* @return The starting minute of this range
*/
public int getStartMinute() {
return this.start;
}
@Override
public boolean includesDate(Calendar cal) {
int minute = cal.get(Calendar.MINUTE);
if (minute == this.start || minute == this.end) {
return true;
}
Calendar compareCal = (Calendar) cal.clone();
compareCal.set(Calendar.MINUTE, this.start);
while (compareCal.get(Calendar.MINUTE) != this.end) {
if (compareCal.get(Calendar.MINUTE) == minute) {
return true;
}
compareCal.add(Calendar.MINUTE, 1);
}
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.MINUTE, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.MINUTE, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
if (includesDate(next)) {
next.add(Calendar.MINUTE, 1);
}
while (!includesDate(next)) {
next.add(Calendar.MINUTE, 1);
}
if (cal.get(Calendar.HOUR_OF_DAY) != next.get(Calendar.HOUR_OF_DAY)) {
context.hourBumped = true;
}
return next;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", end = " + this.end;
}
}
/** A temporal expression that represents a month range. */
public static class MonthRange extends TemporalExpression {
protected final int end;
protected final int start;
public MonthRange(int month) {
this(month, month);
}
/**
* @param start An integer in the range of <code>Calendar.JANUARY</code>
* to <code>Calendar.UNDECIMBER</code>
* @param end An integer in the range of <code>Calendar.JANUARY</code>
* to <code>Calendar.UNDECIMBER</code>
*/
public MonthRange(int start, int end) {
if (start < Calendar.JANUARY || start > Calendar.UNDECIMBER) {
throw new IllegalArgumentException("Invalid start argument");
}
if (end < Calendar.JANUARY || end > Calendar.UNDECIMBER) {
throw new IllegalArgumentException("Invalid end argument");
}
this.sequence = SEQUENCE_MONTH_RANGE + start;
this.start = start;
this.end = end;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
MonthRange that = (MonthRange) obj;
return this.start == that.start && this.end == that.end;
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = (Calendar) cal.clone();
first.set(Calendar.DAY_OF_MONTH, 1);
while (!includesDate(first)) {
first.add(Calendar.MONTH, 1);
}
return first;
}
/** Returns the ending month of this range.
* @return The ending month of this range
*/
public int getEndMonth() {
return this.end;
}
/** Returns the starting month of this range.
* @return The starting month of this range
*/
public int getStartMonth() {
return this.start;
}
@Override
public boolean includesDate(Calendar cal) {
int month = cal.get(Calendar.MONTH);
if (month == this.start || month == this.end) {
return true;
}
Calendar compareCal = (Calendar) cal.clone();
while (compareCal.get(Calendar.MONTH) != this.start) {
compareCal.add(Calendar.MONTH, 1);
}
while (compareCal.get(Calendar.MONTH) != this.end) {
if (compareCal.get(Calendar.MONTH) == month) {
return true;
}
compareCal.add(Calendar.MONTH, 1);
}
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
Calendar checkCal = (Calendar) cal.clone();
checkCal.add(Calendar.MONTH, -1);
while (!includesDate(checkCal)) {
if (expressionToTest.includesDate(checkCal)) {
return true;
}
checkCal.add(Calendar.MONTH, -1);
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = (Calendar) cal.clone();
next.set(Calendar.DAY_OF_MONTH, 1);
next.add(Calendar.MONTH, 1);
while (!includesDate(next)) {
next.add(Calendar.MONTH, 1);
}
return next;
}
@Override
public String toString() {
return super.toString() + ", start = " + this.start + ", end = " + this.end;
}
}
/** A temporal expression that represents a null expression. */
public static class Null extends TemporalExpression {
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public Calendar first(Calendar cal) {
return null;
}
@Override
public boolean includesDate(Calendar cal) {
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
return null;
}
}
/** A temporal expression that provides a substitution for an excluded temporal expression. */
public static class Substitution extends TemporalExpression {
protected final TemporalExpression excluded;
protected final TemporalExpression included;
protected final TemporalExpression substitute;
public Substitution(TemporalExpression included, TemporalExpression excluded, TemporalExpression substitute) {
if (included == null) {
throw new IllegalArgumentException("included argument cannot be null");
}
if (excluded == null) {
throw new IllegalArgumentException("excluded argument cannot be null");
}
if (substitute == null) {
throw new IllegalArgumentException("substitute argument cannot be null");
}
this.included = included;
this.excluded = excluded;
this.substitute = substitute;
if (containsExpression(this)) {
throw new IllegalArgumentException("recursive expression");
}
this.sequence = included.sequence;
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
protected boolean containsExpression(TemporalExpression expression) {
return this.included.containsExpression(expression) || this.excluded.containsExpression(expression);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
Substitution that = (Substitution) obj;
return this.included.equals(that.included) && this.excluded.equals(that.excluded) && this.substitute.equals(that.substitute);
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
Calendar first = this.included.first(cal);
if (first != null && this.excluded.includesDate(first)) {
first = this.substitute.first(first);
}
return first;
}
/** Returns the excluded expression.
* @return The excluded <code>TemporalExpression</code>
*/
public TemporalExpression getExcluded() {
return this.excluded;
}
/** Returns the included expression.
* @return The included <code>TemporalExpression</code>
*/
public TemporalExpression getIncluded() {
return this.included;
}
/** Returns the substitute expression.
* @return The substitute <code>TemporalExpression</code>
*/
public TemporalExpression getSubstitute() {
return this.substitute;
}
@Override
public boolean includesDate(Calendar cal) {
if (this.included.includesDate(cal)) {
return true;
}
return this.substitute.isSubstitutionCandidate(cal, this.excluded);
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
return this.substitute.isSubstitutionCandidate(cal, expressionToTest);
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar next = this.included.next(cal, context);
if (next != null && this.excluded.includesDate(next)) {
next = this.substitute.next(next, context);
}
return next;
}
@Override
public String toString() {
return super.toString() + ", included = " + this.included + ", excluded = " + this.excluded + ", substitute = " + this.substitute;
}
}
/** A temporal expression that represents a mathematical union of all of its
* member expressions. */
public static class Union extends TemporalExpression {
protected final Set<TemporalExpression> expressionSet;
public Union(Set<TemporalExpression> expressionSet) {
if (expressionSet == null) {
throw new IllegalArgumentException("expressionSet argument cannot be null");
}
this.expressionSet = expressionSet;
if (containsExpression(this)) {
throw new IllegalArgumentException("recursive expression");
}
if (this.expressionSet.size() > 0) {
TemporalExpression that = this.expressionSet.iterator().next();
this.sequence = that.sequence;
}
if (Debug.verboseOn()) {
Debug.logVerbose("Created " + this, module);
}
}
@Override
public void accept(TemporalExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
protected boolean containsExpression(TemporalExpression expression) {
for (TemporalExpression setItem : this.expressionSet) {
if (setItem.containsExpression(expression)) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
return this.expressionSet.equals(((Union) obj).expressionSet);
} catch (ClassCastException e) {}
return false;
}
@Override
public Calendar first(Calendar cal) {
for (TemporalExpression expression : this.expressionSet) {
Calendar first = expression.first(cal);
if (first != null && includesDate(first)) {
return first;
}
}
return null;
}
/** Returns the member expression <code>Set</code>. The
* returned set is unmodifiable.
* @return The member expression <code>Set</code>
*/
public Set<TemporalExpression> getExpressionSet() {
return Collections.unmodifiableSet(this.expressionSet);
}
@Override
public boolean includesDate(Calendar cal) {
for (TemporalExpression expression : this.expressionSet) {
if (expression.includesDate(cal)) {
return true;
}
}
return false;
}
@Override
public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) {
for (TemporalExpression expression : this.expressionSet) {
if (expression.isSubstitutionCandidate(cal, expressionToTest)) {
return true;
}
}
return false;
}
@Override
public Calendar next(Calendar cal, ExpressionContext context) {
Calendar result = null;
for (TemporalExpression expression : this.expressionSet) {
Calendar next = expression.next(cal, context);
if (next != null) {
if (result == null || next.before(result)) {
result = next;
}
}
}
return result;
}
@Override
public String toString() {
return super.toString() + ", size = " + this.expressionSet.size();
}
}
}