/*
* Copyright 2014 DataGenerator Contributors
*
* 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 org.finra.datagenerator.engine.scxml.tags.boundary;
import org.finra.datagenerator.consumer.EquivalenceClassTransformer;
import org.finra.datagenerator.engine.scxml.tags.CustomTagExtension;
import org.finra.datagenerator.engine.scxml.tags.boundary.action.BoundaryActionDate;
import org.joda.time.DateTime;
import org.joda.time.DateTimeField;
import org.joda.time.LocalDate;
import org.joda.time.chrono.GregorianChronology;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @param <T>
*/
public abstract class BoundaryDate<T extends BoundaryActionDate> implements CustomTagExtension<T> {
/**
* @param action an Action of the type handled by this class
* @param positive a boolean denoting positive or negative cases
* @return a list of Strings that contain the states
*/
public List<String> setParameters(T action, boolean positive) {
String earliest = action.getEarliest();
String latest = action.getLatest();
boolean onlyBusinessDays = true;
boolean nullable = true;
if (!action.getNullable().equals("true")) {
nullable = false;
}
if (earliest == null) {
earliest = "1970-01-01";
}
if (latest == null) {
Calendar currDate = Calendar.getInstance();
latest = new SimpleDateFormat("yyyy-MM-dd").format(currDate.getTime());
}
if (!action.getOnlyBusinessDays().equalsIgnoreCase("true")) {
onlyBusinessDays = false;
}
if (positive) {
return positiveCase(nullable, earliest, latest, onlyBusinessDays);
} else {
return negativeCase(nullable, earliest, latest);
}
}
/**
* Checks if the date is a holiday
*
* @param dateString the date
* @return true if it is a holiday, false otherwise
*/
public boolean isHoliday(String dateString) {
boolean isHoliday = false;
for (Holiday date : EquivalenceClassTransformer.HOLIDAYS) {
if (convertToReadableDate(date.forYear(Integer.parseInt(dateString.substring(0, 4)))).equals(dateString)) {
isHoliday = true;
}
}
return isHoliday;
}
/**
* Takes a date, and retrieves the next business day
*
* @param dateString the date
* @param onlyBusinessDays only business days
* @return a string containing the next business day
*/
public String getNextDay(String dateString, boolean onlyBusinessDays) {
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime date = parser.parseDateTime(dateString).plusDays(1);
Calendar cal = Calendar.getInstance();
cal.setTime(date.toDate());
if (onlyBusinessDays) {
if (cal.get(Calendar.DAY_OF_WEEK) == 1 || cal.get(Calendar.DAY_OF_WEEK) == 7
|| isHoliday(date.toString().substring(0, 10))) {
return getNextDay(date.toString().substring(0, 10), true);
} else {
return parser.print(date);
}
} else {
return parser.print(date);
}
}
/**
* Takes a date, and returns the previous business day
*
* @param dateString the date
* @param onlyBusinessDays only business days
* @return the previous business day
*/
public String getPreviousDay(String dateString, boolean onlyBusinessDays) {
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime date = parser.parseDateTime(dateString).minusDays(1);
Calendar cal = Calendar.getInstance();
cal.setTime(date.toDate());
if (onlyBusinessDays) {
if (cal.get(Calendar.DAY_OF_WEEK) == 1 || cal.get(Calendar.DAY_OF_WEEK) == 7
|| isHoliday(date.toString().substring(0, 10))) {
return getPreviousDay(date.toString().substring(0, 10), true);
} else {
return parser.print(date);
}
} else {
return parser.print(date);
}
}
/**
* @param isNullable isNullable
* @param earliest lower boundary date
* @param latest upper boundary date
* @param onlyBusinessDays only business days
* @return a list of boundary dates
*/
public List<String> positiveCase(boolean isNullable, String earliest, String latest, boolean onlyBusinessDays) {
List<String> values = new LinkedList<>();
if (earliest.equalsIgnoreCase(latest)) {
values.add(earliest);
if (isNullable) {
values.add("");
}
return values;
}
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime earlyDate = parser.parseDateTime(earliest);
DateTime lateDate = parser.parseDateTime(latest);
String earlyDay = parser.print(earlyDate);
String nextDay = getNextDay(earlyDate.toString().substring(0, 10), onlyBusinessDays);
String prevDay = getPreviousDay(lateDate.toString().substring(0, 10), onlyBusinessDays);
String lateDay = parser.print(lateDate);
values.add(earlyDay);
values.add(nextDay);
values.add(prevDay);
values.add(lateDay);
if (isNullable) {
values.add("");
}
return values;
}
/**
* @param isNullable isNullable
* @param earliest lower boundary date
* @param latest upper boundary date
* @return a list of boundary dates
*/
public List<String> negativeCase(boolean isNullable, String earliest, String latest) {
List<String> values = new LinkedList<>();
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime earlyDate = parser.parseDateTime(earliest);
DateTime lateDate = parser.parseDateTime(latest);
String prevDay = parser.print(earlyDate.minusDays(1));
String nextDay = parser.print(lateDate.plusDays(1));
values.add(prevDay);
values.add(nextDay);
values.add(nextDay.substring(5, 7) + "-" + nextDay.substring(8, 10) + "-" + nextDay.substring(0, 4));
values.add(getRandomHoliday(earliest, latest));
if (!isNullable) {
values.add("");
}
return values;
}
/**
* Takes a String and converts it to a Date
*
* @param dateString the date
* @return Date denoted by dateString
*/
public Date toDate(String dateString) {
Date date = null;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
date = df.parse(dateString);
} catch (ParseException ex) {
System.out.println(ex.fillInStackTrace());
}
return date;
}
/**
* Grab random holiday from the equivalence class that falls between the two dates
*
* @param earliest the earliest date parameter as defined in the model
* @param latest the latest date parameter as defined in the model
* @return a holiday that falls between the dates
*/
public String getRandomHoliday(String earliest, String latest) {
String dateString = "";
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime earlyDate = parser.parseDateTime(earliest);
DateTime lateDate = parser.parseDateTime(latest);
List<Holiday> holidays = new LinkedList<>();
int min = Integer.parseInt(earlyDate.toString().substring(0, 4));
int max = Integer.parseInt(lateDate.toString().substring(0, 4));
int range = max - min + 1;
int randomYear = (int) (Math.random() * range) + min;
for (Holiday s : EquivalenceClassTransformer.HOLIDAYS) {
holidays.add(s);
}
Collections.shuffle(holidays);
for (Holiday holiday : holidays) {
dateString = convertToReadableDate(holiday.forYear(randomYear));
if (toDate(dateString).after(toDate(earliest)) && toDate(dateString).before(toDate(latest))) {
break;
}
}
return dateString;
}
/**
* Given a year, month, and day, find the number of occurrences of that day in the month
*
* @param year the year
* @param month the month
* @param day the day
* @return the number of occurrences of the day in the month
*/
public int numOccurrences(int year, int month, int day) {
DateTimeFormatter parser = ISODateTimeFormat.date();
DateTime date = parser.parseDateTime(year + "-" + month + "-" + "01");
Calendar cal = Calendar.getInstance();
cal.setTime(date.toDate());
GregorianChronology calendar = GregorianChronology.getInstance();
DateTimeField field = calendar.dayOfMonth();
int days = 0;
int count = 0;
int num = field.getMaximumValue(new LocalDate(year, month, day, calendar));
while (days < num) {
if (cal.get(Calendar.DAY_OF_WEEK) == day) {
count++;
}
date = date.plusDays(1);
cal.setTime(date.toDate());
days++;
}
return count;
}
/**
* Convert the holiday format from EquivalenceClassTransformer into a date format
*
* @param holiday the date
* @return a date String in the format yyyy-MM-dd
*/
public String convertToReadableDate(Holiday holiday) {
DateTimeFormatter parser = ISODateTimeFormat.date();
if (holiday.isInDateForm()) {
String month = Integer.toString(holiday.getMonth()).length() < 2
? "0" + holiday.getMonth() : Integer.toString(holiday.getMonth());
String day = Integer.toString(holiday.getDayOfMonth()).length() < 2
? "0" + holiday.getDayOfMonth() : Integer.toString(holiday.getDayOfMonth());
return holiday.getYear() + "-" + month + "-" + day;
} else {
/*
* 5 denotes the final occurrence of the day in the month. Need to find actual
* number of occurrences
*/
if (holiday.getOccurrence() == 5) {
holiday.setOccurrence(numOccurrences(holiday.getYear(), holiday.getMonth(),
holiday.getDayOfWeek()));
}
DateTime date = parser.parseDateTime(holiday.getYear() + "-"
+ holiday.getMonth() + "-" + "01");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date.toDate());
int count = 0;
while (count < holiday.getOccurrence()) {
if (calendar.get(Calendar.DAY_OF_WEEK) == holiday.getDayOfWeek()) {
count++;
if (count == holiday.getOccurrence()) {
break;
}
}
date = date.plusDays(1);
calendar.setTime(date.toDate());
}
return date.toString().substring(0, 10);
}
}
/**
* @param action an Action of the type handled by this class
* @param possibleStateList a current list of possible states produced so far from
* expanding a model state
* @param variableValue a list storing the values
* @return a list of Maps containing the cross product of all states
*/
public List<Map<String, String>> returnStates(T action, List<Map<String, String>> possibleStateList, List<String> variableValue) {
List<Map<String, String>> states = new LinkedList<>();
for (Map<String, String> p : possibleStateList) {
for (String s : variableValue) {
HashMap<String, String> n = new HashMap<>(p);
n.put(action.getName(), s);
states.add(n);
}
}
return states;
}
}