package org.springframework.scheduling.support;
import java.time.DateTimeException;
import java.time.temporal.Temporal;
import java.time.temporal.ValueRange;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
final class BitsCronField extends CronField {
private static final long MASK = 0xFFFFFFFFFFFFFFFFL;
@Nullable
private static BitsCronField zeroNanos = null;
private long bits;
private BitsCronField(Type type) {
super(type);
}
public static BitsCronField zeroNanos() {
if (zeroNanos == null) {
BitsCronField field = new BitsCronField(Type.NANO);
field.setBit(0);
zeroNanos = field;
}
return zeroNanos;
}
public static BitsCronField parseSeconds(String value) {
return parseField(value, Type.SECOND);
}
public static BitsCronField parseMinutes(String value) {
return BitsCronField.parseField(value, Type.MINUTE);
}
public static BitsCronField parseHours(String value) {
return BitsCronField.parseField(value, Type.HOUR);
}
public static BitsCronField parseDaysOfMonth(String value) {
return parseDate(value, Type.DAY_OF_MONTH);
}
public static BitsCronField parseMonth(String value) {
return BitsCronField.parseField(value, Type.MONTH);
}
public static BitsCronField parseDaysOfWeek(String value) {
BitsCronField result = parseDate(value, Type.DAY_OF_WEEK);
if (result.getBit(0)) {
result.setBit(7);
result.clearBit(0);
}
return result;
}
private static BitsCronField parseDate(String value, BitsCronField.Type type) {
if (value.equals("?")) {
value = "*";
}
return BitsCronField.parseField(value, type);
}
private static BitsCronField parseField(String value, Type type) {
Assert.hasLength(value, "Value must not be empty");
Assert.notNull(type, "Type must not be null");
try {
BitsCronField result = new BitsCronField(type);
String[] fields = StringUtils.delimitedListToStringArray(value, ",");
for (String field : fields) {
int slashPos = field.indexOf('/');
if (slashPos == -1) {
ValueRange range = parseRange(field, type);
result.setBits(range);
}
else {
String rangeStr = field.substring(0, slashPos);
String deltaStr = field.substring(slashPos + 1);
ValueRange range = parseRange(rangeStr, type);
if (rangeStr.indexOf('-') == -1) {
range = ValueRange.of(range.getMinimum(), type.range().getMaximum());
}
int delta = Integer.parseInt(deltaStr);
if (delta <= 0) {
throw new IllegalArgumentException("Incrementer delta must be 1 or higher");
}
result.setBits(range, delta);
}
}
return result;
}
catch (DateTimeException | IllegalArgumentException ex) {
String msg = ex.getMessage() + " '" + value + "'";
throw new IllegalArgumentException(msg, ex);
}
}
private static ValueRange parseRange(String value, Type type) {
if (value.equals("*")) {
return type.range();
}
else {
int hyphenPos = value.indexOf('-');
if (hyphenPos == -1) {
int result = type.checkValidValue(Integer.parseInt(value));
return ValueRange.of(result, result);
}
else {
int min = Integer.parseInt(value.substring(0, hyphenPos));
int max = Integer.parseInt(value.substring(hyphenPos + 1));
min = type.checkValidValue(min);
max = type.checkValidValue(max);
return ValueRange.of(min, max);
}
}
}
@Nullable
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
int current = type().get(temporal);
int next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
if (next == current) {
return temporal;
}
else {
int count = 0;
current = type().get(temporal);
while (current != next && count++ < CronExpression.MAX_ATTEMPTS) {
temporal = type().elapseUntil(temporal, next);
current = type().get(temporal);
}
if (count >= CronExpression.MAX_ATTEMPTS) {
return null;
}
return type().reset(temporal);
}
}
boolean getBit(int index) {
return (this.bits & (1L << index)) != 0;
}
private int nextSetBit(int fromIndex) {
long result = this.bits & (MASK << fromIndex);
if (result != 0) {
return Long.numberOfTrailingZeros(result);
}
else {
return -1;
}
}
private void setBits(ValueRange range) {
if (range.getMinimum() == range.getMaximum()) {
setBit((int) range.getMinimum());
}
else {
long minMask = MASK << range.getMinimum();
long maxMask = MASK >>> - (range.getMaximum() + 1);
this.bits |= (minMask & maxMask);
}
}
private void setBits(ValueRange range, int delta) {
if (delta == 1) {
setBits(range);
}
else {
for (int i = (int) range.getMinimum(); i <= range.getMaximum(); i += delta) {
setBit(i);
}
}
}
private void setBit(int index) {
this.bits |= (1L << index);
}
private void clearBit(int index) {
this.bits &= ~(1L << index);
}
@Override
public int hashCode() {
return Long.hashCode(this.bits);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BitsCronField)) {
return false;
}
BitsCronField other = (BitsCronField) o;
return type() == other.type() && this.bits == other.bits;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(type().toString());
builder.append(" {");
int i = nextSetBit(0);
if (i != -1) {
builder.append(i);
i = nextSetBit(i+1);
while (i != -1) {
builder.append(", ");
builder.append(i);
i = nextSetBit(i+1);
}
}
builder.append('}');
return builder.toString();
}
}