package org.springframework.scheduling.support;
import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
final class QuartzCronField extends CronField {
private static final TemporalAdjuster lastWeekdayOfMonth = temporal -> {
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
int dayOfWeek = lastDayOfMonth.get(ChronoField.DAY_OF_WEEK);
if (dayOfWeek == 6) {
return lastDayOfMonth.minus(1, ChronoUnit.DAYS);
}
else if (dayOfWeek == 7) {
return lastDayOfMonth.minus(2, ChronoUnit.DAYS);
}
else {
return lastDayOfMonth;
}
};
private final Type rollForwardType;
private final TemporalAdjuster adjuster;
private final String value;
private QuartzCronField(Type type, TemporalAdjuster adjuster, String value) {
this(type, type, adjuster, value);
}
private QuartzCronField(Type type, Type rollForwardType, TemporalAdjuster adjuster, String value) {
super(type);
this.adjuster = adjuster;
this.value = value;
this.rollForwardType = rollForwardType;
}
public static boolean isQuartzDaysOfMonthField(String value) {
return value.contains("L") || value.contains("W");
}
public static QuartzCronField parseDaysOfMonth(String value) {
int idx = value.lastIndexOf('L');
if (idx != -1) {
TemporalAdjuster adjuster;
if (idx != 0) {
throw new IllegalArgumentException("Unrecognized characters before 'L' in '" + value + "'");
}
else if (value.length() == 2 && value.charAt(1) == 'W') {
adjuster = lastWeekdayOfMonth;
}
else {
if (value.length() == 1) {
adjuster = TemporalAdjusters.lastDayOfMonth();
}
else {
int offset = Integer.parseInt(value.substring(idx + 1));
if (offset >= 0) {
throw new IllegalArgumentException("Offset '" + offset + " should be < 0 '" + value + "'");
}
adjuster = lastDayWithOffset(offset);
}
}
return new QuartzCronField(Type.DAY_OF_MONTH, adjuster, value);
}
idx = value.lastIndexOf('W');
if (idx != -1) {
if (idx == 0) {
throw new IllegalArgumentException("No day-of-month before 'W' in '" + value + "'");
}
else if (idx != value.length() - 1) {
throw new IllegalArgumentException("Unrecognized characters after 'W' in '" + value + "'");
}
else {
int dayOfMonth = Integer.parseInt(value.substring(0, idx));
dayOfMonth = Type.DAY_OF_MONTH.checkValidValue(dayOfMonth);
TemporalAdjuster adjuster = weekdayNearestTo(dayOfMonth);
return new QuartzCronField(Type.DAY_OF_MONTH, adjuster, value);
}
}
throw new IllegalArgumentException("No 'L' or 'W' found in '" + value + "'");
}
public static boolean isQuartzDaysOfWeekField(String value) {
return value.contains("L") || value.contains("#");
}
public static QuartzCronField parseDaysOfWeek(String value) {
int idx = value.lastIndexOf('L');
if (idx != -1) {
if (idx != value.length() - 1) {
throw new IllegalArgumentException("Unrecognized characters after 'L' in '" + value + "'");
}
else {
TemporalAdjuster adjuster;
if (idx == 0) {
throw new IllegalArgumentException("No day-of-week before 'L' in '" + value + "'");
}
else {
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
adjuster = TemporalAdjusters.lastInMonth(dayOfWeek);
}
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
}
}
idx = value.lastIndexOf('#');
if (idx != -1) {
if (idx == 0) {
throw new IllegalArgumentException("No day-of-week before '#' in '" + value + "'");
}
else if (idx == value.length() - 1) {
throw new IllegalArgumentException("No ordinal after '#' in '" + value + "'");
}
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
int ordinal = Integer.parseInt(value.substring(idx + 1));
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
}
throw new IllegalArgumentException("No 'L' or '#' found in '" + value + "'");
}
private static DayOfWeek parseDayOfWeek(String value) {
int dayOfWeek = Integer.parseInt(value);
if (dayOfWeek == 0) {
dayOfWeek = 7;
}
try {
return DayOfWeek.of(dayOfWeek);
}
catch (DateTimeException ex) {
String msg = ex.getMessage() + " '" + value + "'";
throw new IllegalArgumentException(msg, ex);
}
}
private static TemporalAdjuster lastDayWithOffset(int offset) {
Assert.isTrue(offset < 0, "Offset should be < 0");
return temporal -> {
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
return lastDayOfMonth.plus(offset, ChronoUnit.DAYS);
};
}
private static TemporalAdjuster weekdayNearestTo(int dayOfMonth) {
return temporal -> {
int current = Type.DAY_OF_MONTH.get(temporal);
int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
if ((current == dayOfMonth && dayOfWeek < 6) ||
(dayOfWeek == 5 && current == dayOfMonth - 1) ||
(dayOfWeek == 1 && current == dayOfMonth + 1) ||
(dayOfWeek == 1 && dayOfMonth == 1 && current == 3)) {
return temporal;
}
int count = 0;
while (count++ < CronExpression.MAX_ATTEMPTS) {
temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth);
current = Type.DAY_OF_MONTH.get(temporal);
if (current == dayOfMonth) {
dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
if (dayOfWeek == 6) {
if (dayOfMonth != 1) {
return temporal.minus(1, ChronoUnit.DAYS);
}
else {
return temporal.plus(2, ChronoUnit.DAYS);
}
}
else if (dayOfWeek == 7) {
return temporal.plus(1, ChronoUnit.DAYS);
}
else {
return temporal;
}
}
}
return null;
};
}
@SuppressWarnings("unchecked")
private static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
return (T) temporal;
}
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
T result = adjust(temporal);
if (result != null) {
if (result.compareTo(temporal) < 0) {
temporal = this.rollForwardType.rollForward(temporal);
result = adjust(temporal);
}
}
return result;
}
@Nullable
@SuppressWarnings("unchecked")
private <T extends Temporal & Comparable<? super T>> T adjust(T temporal) {
return (T) this.adjuster.adjustInto(temporal);
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof QuartzCronField)) {
return false;
}
QuartzCronField other = (QuartzCronField) o;
return type() == other.type() &&
this.value.equals(other.value);
}
@Override
public String toString() {
return type() + " '" + this.value + "'";
}
}