| /******************************************************************************* |
| * 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.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.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.ofbiz.base.util.DateRange range; |
| |
| public DateRange(Date date) { |
| this(date, date); |
| } |
| |
| public DateRange(Date start, Date end) { |
| this.range = new org.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.ofbiz.base.util.DateRange</code>. |
| * @return The contained <code>org.ofbiz.base.util.DateRange</code> |
| */ |
| public org.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) { |
| 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(); |
| } |
| } |
| } |