/******************************************************************************* * 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(); } } }