/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kr.debop4j.timeperiod.calendars;
import com.google.common.collect.Iterables;
import kr.debop4j.timeperiod.*;
import kr.debop4j.timeperiod.timerange.*;
import kr.debop4j.timeperiod.tools.TimeSpec;
import kr.debop4j.timeperiod.tools.Times;
import lombok.Getter;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import static kr.debop4j.core.Guard.shouldNotBeNull;
/**
* 특정 기간에 대한 필터링 정보를 기반으로 기간들을 필터링 할 수 있도록 특정 기간을 탐색하는 Visitor입니다.
*
* @author 배성혁 sunghyouk.bae@gmail.com
* @since 13. 5. 19. 오전 3:19
*/
public abstract class CalendarVisitor<F extends ICalendarVisitorFilter, C extends ICalendarVisitorContext> {
private static final Logger log = LoggerFactory.getLogger(CalendarVisitor.class);
private static final boolean isTraceEnabled = log.isTraceEnabled();
private static final boolean isDebugEnabled = log.isDebugEnabled();
@Getter private final F filter;
@Getter private final ITimePeriod limits;
@Getter private final SeekDirection seekDirection;
@Getter private final ITimeCalendar calendar;
public CalendarVisitor(F filter, ITimePeriod limits) {
this(filter, limits, SeekDirection.Forward, null);
}
public CalendarVisitor(F filter, ITimePeriod limits, SeekDirection seekDir) {
this(filter, limits, seekDir, null);
}
public CalendarVisitor(F filter, ITimePeriod limits, ITimeCalendar calendar) {
this(filter, limits, SeekDirection.Forward, calendar);
}
public CalendarVisitor(F filter, ITimePeriod limits, SeekDirection seekDir, ITimeCalendar calendar) {
shouldNotBeNull(filter, "filter");
shouldNotBeNull(limits, "limits");
this.filter = filter;
this.limits = limits;
this.seekDirection = (seekDir != null) ? seekDir : SeekDirection.Forward;
this.calendar = (calendar != null) ? calendar : TimeCalendar.getDefault();
}
protected final void startPeriodVisit(C context) {
startPeriodVisit(this.limits, context);
}
// protected final void startPeriodVisit(ITimePeriod period, C context) {
// if (isTraceEnabled)
// log.trace("기간에 대한 탐색을 시작합니다... period=[{}], context=[{}]", period, context);
// shouldNotBeNull(period, "period");
//
// if (period.isMoment()) return;
//
// onVisitStart();
//
// YearRangeCollection years = (calendar != null)
// ? new YearRangeCollection(period.getStart().getYear(), period.getEnd().getYear() - period.getStart().getYear() + 1, calendar)
// : new YearRangeCollection(period.getStart().getYear(), period.getEnd().getYear() - period.getStart().getYear() + 1, calendar);
//
// if (onVisitYears(years, context) && enterYears(years, context)) {
// List<YearRange> yearsToVisit = years.getYears();
//
// if (seekDirection == SeekDirection.Backward)
// Collections.sort(yearsToVisit, Times.getEndComparator());
//
// for (YearRange year : yearsToVisit) {
// if (isTraceEnabled) log.trace("year를 탐색합니다... year=[{}]", year.getYear());
//
// if (!year.overlapsWith(period))
// continue;
// if (!onVisitYear(year, context))
// continue;
// if (!enterMonths(year, context))
// continue;
//
// List<MonthRange> monthsToVisit = year.getMonths();
// if (seekDirection == SeekDirection.Backward)
// Collections.sort(monthsToVisit, Times.getEndComparator());
//
// // TODO: Parallels.runEach(); 를 이용해 병렬로 수행하는 것 테스트 할 것
// for (MonthRange month : monthsToVisit) {
// if (isTraceEnabled) log.trace("month를 탐색합니다... month=[{}]", month);
//
// if (!month.overlapsWith(period))
// continue;
// if (!onVisitMonth(month, context))
// continue;
// if (!enterDays(month, context))
// continue;
//
// List<DayRange> daysToVisit = month.getDays();
//
// if (seekDirection == SeekDirection.Backward)
// Collections.sort(daysToVisit, Times.getEndComparator());
//
// for (DayRange day : daysToVisit) {
// if (isTraceEnabled) log.trace("day를 탐색합니다... day=[{}]", day);
//
// if (!day.overlapsWith(period))
// continue;
// if (!onVisitDay(day, context))
// continue;
// if (!enterHours(day, context))
// continue;
//
// List<HourRange> hoursToVisit = day.getHours();
//
// if (seekDirection == SeekDirection.Backward)
// Collections.sort(hoursToVisit, Times.getEndComparator());
//
// for (HourRange hour : hoursToVisit) {
// log.trace("hour를 탐색합니다... hour=[{}]", hour);
//
// if (!hour.overlapsWith(period))
// continue;
// if (!onVisitHour(hour, context))
// continue;
//
// enterMinutes(hour, context);
// }
// }
// }
// }
// }
//
// onVisitEnd();
//
// if (isTraceEnabled)
// log.trace("기간에 대한 탐색을 완료했습니다!!! period=[{}], context=[{}]", period, context);
// }
protected final void startPeriodVisit(final ITimePeriod period, final C context) {
if (isTraceEnabled)
log.trace("기간에 대한 탐색을 시작합니다... period=[{}], context=[{}]", period, context);
shouldNotBeNull(period, "period");
if (period.isMoment()) return;
onVisitStart();
YearRangeCollection years = (calendar != null)
? new YearRangeCollection(period.getStart().getYear(), period.getEnd().getYear() - period.getStart().getYear() + 1, calendar)
: new YearRangeCollection(period.getStart().getYear(), period.getEnd().getYear() - period.getStart().getYear() + 1, calendar);
if (onVisitYears(years, context) && enterYears(years, context)) {
List<YearRange> yearsToVisit = years.getYears();
if (seekDirection == SeekDirection.Backward)
Collections.sort(yearsToVisit, Times.getEndComparator());
for (YearRange year : yearsToVisit) {
if (isTraceEnabled) log.trace("year를 탐색합니다... year=[{}]", year.getYear());
if (!year.overlapsWith(period))
continue;
if (!onVisitYear(year, context))
continue;
if (!enterMonths(year, context))
continue;
List<MonthRange> monthsToVisit = year.getMonths();
if (seekDirection == SeekDirection.Backward)
Collections.sort(monthsToVisit, Times.getEndComparator());
for (MonthRange month : monthsToVisit) {
if (isTraceEnabled) log.trace("month를 탐색합니다... month=[{}]", month);
if (!month.overlapsWith(period))
continue;
if (!onVisitMonth(month, context))
continue;
if (!enterDays(month, context))
continue;
List<DayRange> daysToVisit = month.getDays();
if (seekDirection == SeekDirection.Backward)
Collections.sort(daysToVisit, Times.getEndComparator());
for (DayRange day : daysToVisit) {
if (isTraceEnabled) log.trace("day를 탐색합니다... day=[{}]", day);
if (!day.overlapsWith(period))
continue;
if (!onVisitDay(day, context))
continue;
if (!enterHours(day, context))
continue;
List<HourRange> hoursToVisit = day.getHours();
if (seekDirection == SeekDirection.Backward)
Collections.sort(hoursToVisit, Times.getEndComparator());
for (HourRange hour : hoursToVisit) {
log.trace("hour를 탐색합니다... hour=[{}]", hour);
if (!hour.overlapsWith(period))
continue;
if (!onVisitHour(hour, context))
continue;
enterMinutes(hour, context);
}
}
}
}
}
onVisitEnd();
if (isTraceEnabled) log.trace("기간에 대한 탐색을 완료했습니다!!! period=[{}], context=[{}]", period, context);
}
protected final YearRange startYearVisit(YearRange year, C context, SeekDirection direction) {
shouldNotBeNull(year, "year");
if (isTraceEnabled)
log.trace("Year 단위로 탐색합니다. year=[{}], context=[{}], direction=[{}]", year, context, direction);
YearRange lastVisited = null;
onVisitStart();
DateTime minStart = TimeSpec.MinPeriodTime;
DateTime maxEnd = TimeSpec.MaxPeriodTime.minusYears(1);
int offset = direction.getValue();
while (year.getStart().compareTo(minStart) > 0 && year.getEnd().compareTo(maxEnd) < 0) {
if (!onVisitYear(year, context)) {
lastVisited = year;
break;
}
year = year.addYears(offset);
}
onVisitEnd();
if (isTraceEnabled) log.trace("마지막 탐색 Year. lastVisited=[{}]", lastVisited);
return lastVisited;
}
protected final MonthRange startMonthVisit(MonthRange month, C context, SeekDirection direction) {
shouldNotBeNull(month, "month");
if (isTraceEnabled)
log.trace("Month 단위로 탐색합니다. month=[{}], context=[{}], direction=[{}]", month, context, direction);
MonthRange lastVisited = null;
onVisitStart();
DateTime minStart = TimeSpec.MinPeriodTime;
DateTime maxEnd = TimeSpec.MaxPeriodTime.minusYears(1);
int offset = direction.getValue();
while (month.getStart().compareTo(minStart) > 0 && month.getEnd().compareTo(maxEnd) < 0) {
if (!onVisitMonth(month, context)) {
lastVisited = month;
break;
}
month = month.addMonths(offset);
}
onVisitEnd();
if (isTraceEnabled) log.trace("마지막 탐색 Month. lastVisited=[{}]", lastVisited);
return lastVisited;
}
protected final DayRange startDayVisit(DayRange day, C context, SeekDirection direction) {
shouldNotBeNull(day, "day");
if (isTraceEnabled)
log.trace("Day 단위로 탐색합니다. day=[{}], context=[{}], direction=[{}]", day, context, direction);
DayRange lastVisited = null;
onVisitStart();
DateTime minStart = TimeSpec.MinPeriodTime;
DateTime maxEnd = TimeSpec.MaxPeriodTime.minusYears(1);
int offset = direction.getValue();
while (day.getStart().compareTo(minStart) > 0 && day.getEnd().compareTo(maxEnd) < 0) {
if (!onVisitDay(day, context)) {
lastVisited = day;
break;
}
day = day.addDays(offset);
}
onVisitEnd();
if (isTraceEnabled) log.trace("마지막 탐색 Day. lastVisited=[{}]", lastVisited);
return lastVisited;
}
protected final HourRange startHourVisit(HourRange hour, C context, SeekDirection direction) {
shouldNotBeNull(hour, "hour");
if (isTraceEnabled)
log.trace("Hour 단위로 탐색합니다. hour=[{}], context=[{}], direction=[{}]", hour, context, direction);
HourRange lastVisited = null;
onVisitStart();
DateTime minStart = TimeSpec.MinPeriodTime;
DateTime maxEnd = TimeSpec.MaxPeriodTime.minusYears(1);
int offset = direction.getValue();
while (hour.getStart().compareTo(minStart) > 0 && hour.getEnd().compareTo(maxEnd) < 0) {
if (!onVisitHour(hour, context)) {
lastVisited = hour;
break;
}
hour = hour.addHours(offset);
}
onVisitEnd();
if (isTraceEnabled) log.trace("마지막 탐색 hour. lastVisited=[{}]", lastVisited);
return lastVisited;
}
protected void onVisitStart() {
if (isTraceEnabled)
log.trace("Calendar 탐색을 시작합니다...");
}
protected boolean checkLimits(ITimePeriod target) {
return getLimits().hasInside(target);
}
protected boolean checkExcludePeriods(ITimePeriod target) {
if (filter.getExcludePeriods().size() == 0)
return true;
return Iterables.size(filter.getExcludePeriods().overlapPeriods(target)) == 0;
}
protected boolean enterYears(YearRangeCollection yearRangeCollection, C context) {
return true;
}
protected boolean enterMonths(YearRange yearRange, C context) {
return true;
}
protected boolean enterDays(MonthRange month, C context) {
return true;
}
protected boolean enterHours(DayRange day, C context) {
return true;
}
protected boolean enterMinutes(HourRange hour, C context) {
return true;
}
protected boolean onVisitYears(YearRangeCollection years, C context) {
return true;
}
protected boolean onVisitYear(YearRange year, C context) {
return true;
}
protected boolean onVisitMonth(MonthRange month, C context) {
return true;
}
protected boolean onVisitDay(DayRange day, C context) {
return true;
}
protected boolean onVisitHour(HourRange hour, C context) {
return true;
}
protected boolean onVisitMinute(MinuteRange minute, C context) {
return true;
}
protected boolean isMatchingYear(YearRange year, C context) {
if (filter.getYears().size() > 0 && !filter.getYears().contains(year.getYear()))
return false;
return checkExcludePeriods(year);
}
protected boolean isMatchingMonth(MonthRange month, C context) {
if (filter.getYears().size() > 0 && !filter.getYears().contains(month.getYear()))
return false;
if (filter.getMonthOfYears().size() > 0 && !filter.getMonthOfYears().contains(month.getMonthOfYear()))
return false;
return checkExcludePeriods(month);
}
protected boolean isMatchingDay(DayRange day, C context) {
if (filter.getYears().size() > 0 && !filter.getYears().contains(day.getYear()))
return false;
if (filter.getMonthOfYears().size() > 0 && !filter.getMonthOfYears().contains(day.getMonthOfYear()))
return false;
if (filter.getDayOfMonths().size() > 0 && !filter.getDayOfMonths().contains(day.getDayOfMonth()))
return false;
if (filter.getWeekDays().size() > 0 && !filter.getWeekDays().contains(day.getDayOfWeek()))
return false;
return checkExcludePeriods(day);
}
protected boolean isMatchingHour(HourRange hour, C context) {
if (filter.getYears().size() > 0 && !filter.getYears().contains(hour.getYear()))
return false;
if (filter.getMonthOfYears().size() > 0 && !filter.getMonthOfYears().contains(hour.getMonthOfYear()))
return false;
if (filter.getDayOfMonths().size() > 0 && !filter.getDayOfMonths().contains(hour.getDayOfMonth()))
return false;
if (filter.getHourOfDays().size() > 0 && !filter.getHourOfDays().contains(hour.getHourOfDay()))
return false;
if (filter.getWeekDays().size() > 0 && !filter.getWeekDays().contains(DayOfWeek.valueOf(hour.getStart().getDayOfWeek())))
return false;
return checkExcludePeriods(hour);
}
protected boolean isMatchingMinute(MinuteRange minute, C context) {
if (filter.getYears().size() > 0 && !filter.getYears().contains(minute.getYear()))
return false;
if (filter.getMonthOfYears().size() > 0 && !filter.getMonthOfYears().contains(minute.getMonthOfYear()))
return false;
if (filter.getDayOfMonths().size() > 0 && !filter.getDayOfMonths().contains(minute.getDayOfMonth()))
return false;
if (filter.getHourOfDays().size() > 0 && !filter.getHourOfDays().contains(minute.getHourOfDay()))
return false;
if (filter.getMinuteOfHours().size() > 0 && !filter.getMinuteOfHours().contains(minute.getMinuteOfHour()))
return false;
return checkExcludePeriods(minute);
}
protected void onVisitEnd() {
if (isTraceEnabled)
log.trace("Calendar 탐색을 종료합니다.");
}
}