package dials.filter.impl;
import dials.dial.DialHelper;
import dials.dial.Dialable;
import dials.filter.FeatureFilter;
import dials.filter.FilterDataException;
import dials.filter.FilterDataHelper;
import dials.filter.StaticDataFilter;
import dials.messages.ContextualMessage;
import dials.messages.DataFilterApplicationMessage;
import dials.model.FeatureModel;
import dials.model.FilterModel;
import org.joda.time.Hours;
import org.joda.time.LocalTime;
import org.joda.time.Minutes;
import org.joda.time.format.DateTimeFormat;
public class TimeWindowFeatureFilter extends FeatureFilter implements StaticDataFilter, Dialable {
public static final String START_TIME = "StartTime";
public static final String END_TIME = "EndTime";
private static final int EXPECTED_PATTERN_LENGTH = 3;
private static final int MINUTES_IN_DAY = 1440;
private LocalTime startTime;
private Integer timeWindowInMinutes;
private boolean crossesMidnight;
@Override
public boolean filter() {
LocalTime now = LocalTime.now();
if (timeWindowInMinutes >= MINUTES_IN_DAY) {
return true;
} else if (crossesMidnight && (now.isAfter(startTime) || now.isBefore(startTime.plusMinutes(timeWindowInMinutes)))) {
return true;
} else if (!crossesMidnight && now.isAfter(startTime) && now.isBefore(startTime.plusMinutes(timeWindowInMinutes))) {
return true;
}
return false;
}
@Override
public void applyStaticData(DataFilterApplicationMessage message) {
FilterDataHelper helper = new FilterDataHelper(message.getFilterData());
applyRequiredData(message, helper);
}
private void applyRequiredData(ContextualMessage message, FilterDataHelper helper) {
boolean success = true;
try {
startTime = helper.getData(START_TIME, LocalTime.class);
recordSuccessfulDataApply(message, START_TIME);
} catch (FilterDataException e) {
recordUnsuccessfulDataApply(message, START_TIME, false, e.getMessage());
success = false;
}
LocalTime endTime = startTime;
try {
endTime = helper.getData(END_TIME, LocalTime.class);
recordSuccessfulDataApply(message, END_TIME);
} catch (FilterDataException e) {
recordUnsuccessfulDataApply(message, END_TIME, false, e.getMessage());
success = false;
}
if (!success) {
abandon(message);
} else if (startTime.isAfter(endTime) || startTime.isEqual(endTime)) {
Minutes minutes = Minutes.minutesBetween(endTime, startTime);
timeWindowInMinutes = MINUTES_IN_DAY - minutes.getMinutes();
crossesMidnight = true;
} else {
Minutes minutes = Minutes.minutesBetween(startTime, endTime);
timeWindowInMinutes = minutes.getMinutes();
}
}
@Override
public void dial(ContextualMessage message, String filterName) {
FeatureModel feature = message.getFeature();
FilterModel filter = feature.getFilter(filterName);
DialHelper helper = new DialHelper(filter.getDial());
String dialPattern = helper.getDialPattern(message);
if (dialPattern.equals(DialHelper.ATTEMPTED)) {
message.performDialAdjustment(feature.getFeatureName(), filterName, START_TIME,
DateTimeFormat.forPattern("HH:mm:ss").print(startTime));
message.performDialAdjustment(feature.getFeatureName(), filterName, END_TIME,
DateTimeFormat.forPattern("HH:mm:ss").print(startTime.plusMinutes(timeWindowInMinutes)));
return;
}
TimeWindowPattern timeToAdd = consumeDialPattern(dialPattern);
if (timeToAdd != null) {
message.getExecutionContext().addExecutionStep("Dial with pattern " + dialPattern + " performed on "
+ getClass().getSimpleName());
calculateNewTimes(timeToAdd);
message.performDialAdjustment(feature.getFeatureName(), filterName, START_TIME,
DateTimeFormat.forPattern("HH:mm:ss").print(startTime));
message.performDialAdjustment(feature.getFeatureName(), filterName, END_TIME,
DateTimeFormat.forPattern("HH:mm:ss").print(startTime.plusMinutes(timeWindowInMinutes)));
message.getExecutionContext().addExecutionStep("Dial successfully executed. New start time is "
+ startTime + " new end time is " + startTime.plusMinutes(timeWindowInMinutes));
if (startTime.toDateTimeToday().isAfter(startTime.plusMinutes(timeWindowInMinutes).toDateTimeToday())) {
message.disableFeature(feature.getFeatureName());
message.getExecutionContext().addExecutionStep("Start time is now after end time, disabling feature.");
}
}
}
/**
* Dial pattern for TimeWindowFeatureFilter is (Integer Unit Direction).
* <p/>
* Examples:
* 1 hour end (Increase Pattern) - Add 1 hour to the end time.
* 5 minutes start (Increase Pattern) - Subtract 5 minutes from the start time.
* -2 hours both (Decrease Pattern) - Subtract 2 hours from the end time, add 2 hours to the start time.
* <p/>
* <p/>
* Unit can be one of (hour, hours, minute, minutes)
* Direction can be one of (start, end, both)
*/
@Override
public TimeWindowPattern consumeDialPattern(String pattern) {
String[] splitPattern = pattern.split(" ");
if (splitPattern.length == EXPECTED_PATTERN_LENGTH) {
try {
return new TimeWindowPattern(Integer.parseInt(splitPattern[0]), splitPattern[1], splitPattern[2]);
} catch (NumberFormatException e) {
return null;
}
}
return null;
}
protected void calculateNewTimes(TimeWindowPattern timeToAdd) {
String unit = timeToAdd.getUnit();
String direction = timeToAdd.getDirection();
int minutesToAdd = 0;
if (unit.toLowerCase().startsWith("hour")) {
minutesToAdd = Hours.hours(timeToAdd.getAmount()).toStandardMinutes().getMinutes();
} else if (unit.toLowerCase().startsWith("minute")) {
minutesToAdd = timeToAdd.getAmount();
}
LocalTime newStartTime;
if (direction.equals("start")) {
newStartTime = startTime.minusMinutes(minutesToAdd);
timeWindowInMinutes += minutesToAdd;
} else if (direction.equals("end")) {
newStartTime = startTime;
timeWindowInMinutes += minutesToAdd;
} else {
newStartTime = startTime.minusMinutes(minutesToAdd);
timeWindowInMinutes += minutesToAdd * 2;
}
if (newStartTime.isAfter(newStartTime.plusMinutes(timeWindowInMinutes)) || timeWindowInMinutes >= MINUTES_IN_DAY) {
crossesMidnight = true;
} else {
crossesMidnight = false;
}
if (timeWindowInMinutes <= MINUTES_IN_DAY) {
switch (direction) {
case "start":
case "both":
startTime = newStartTime;
break;
case "end":
break;
}
} else {
timeWindowInMinutes = MINUTES_IN_DAY;
}
}
protected LocalTime getStartTime() {
return startTime;
}
protected LocalTime getEndTime() {
return startTime.plusMinutes(timeWindowInMinutes);
}
protected boolean getCrossesMidnight() {
return crossesMidnight;
}
protected static class TimeWindowPattern {
private Integer amount;
private String unit;
private String direction;
public TimeWindowPattern(Integer amount, String unit, String direction) {
this.amount = amount;
this.unit = unit;
this.direction = direction;
}
public Integer getAmount() {
return amount;
}
public String getUnit() {
return unit;
}
public String getDirection() {
return direction;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimeWindowPattern that = (TimeWindowPattern) o;
if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
return false;
}
if (direction != null ? !direction.equals(that.direction) : that.direction != null) {
return false;
}
if (unit != null ? !unit.equals(that.unit) : that.unit != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = amount != null ? amount.hashCode() : 0;
result = 31 * result + (unit != null ? unit.hashCode() : 0);
result = 31 * result + (direction != null ? direction.hashCode() : 0);
return result;
}
}
}