change parser

This commit is contained in:
Looly
2022-03-27 12:14:55 +08:00
parent b8569a9837
commit bbcf5f1b54
23 changed files with 512 additions and 437 deletions

View File

@@ -1,7 +1,7 @@
package cn.hutool.cron.pattern;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.parser.CronPatternParser;
import cn.hutool.cron.pattern.parser.PatternParser;
import java.util.Calendar;
import java.util.GregorianCalendar;
@@ -68,6 +68,17 @@ public class CronPattern {
private final String pattern;
private final MatcherTable matcherTable;
/**
* 解析表达式为 CronPattern
*
* @param pattern 表达式
* @return CronPattern
* @since 5.8.0
*/
public static CronPattern of(String pattern) {
return new CronPattern(pattern);
}
/**
* 构造
*
@@ -75,10 +86,11 @@ public class CronPattern {
*/
public CronPattern(String pattern) {
this.pattern = pattern;
this.matcherTable = CronPatternParser.parse(pattern);
this.matcherTable = PatternParser.parse(pattern);
}
// --------------------------------------------------------------------------------------- match start
/**
* 给定时间是否匹配定时任务表达式
*
@@ -104,6 +116,24 @@ public class CronPattern {
return match(calendar, isMatchSecond);
}
/**
* 返回匹配到的下一个时间
*
* @param calendar 时间
* @return 匹配到的下一个时间
*/
public Calendar nextMatchAfter(Calendar calendar) {
final int second = calendar.get(Calendar.SECOND);
final int minute = calendar.get(Calendar.MINUTE);
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
final int month = calendar.get(Calendar.MONTH) + 1;// 月份从1开始
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始0和7都表示周日
final int year = calendar.get(Calendar.YEAR);
return this.matcherTable.nextMatchAfter(second, minute, hour, dayOfMonth, month, dayOfWeek, year, calendar.getTimeZone());
}
/**
* 给定时间是否匹配定时任务表达式
*
@@ -111,7 +141,7 @@ public class CronPattern {
* @param isMatchSecond 是否匹配秒
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(GregorianCalendar calendar, boolean isMatchSecond) {
public boolean match(Calendar calendar, boolean isMatchSecond) {
final int second = isMatchSecond ? calendar.get(Calendar.SECOND) : -1;
final int minute = calendar.get(Calendar.MINUTE);
final int hour = calendar.get(Calendar.HOUR_OF_DAY);

View File

@@ -0,0 +1,23 @@
package cn.hutool.cron.pattern;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.util.StrUtil;
public class CronPatternBuilder implements Builder<String> {
final String[] parts = new String[7];
public static CronPatternBuilder of(){
return new CronPatternBuilder();
}
public CronPatternBuilder set(Part part, String value){
parts[part.ordinal()] = value;
return this;
}
@Override
public String build() {
return StrUtil.join(StrUtil.SPACE, (Object[]) parts);
}
}

View File

@@ -0,0 +1,73 @@
package cn.hutool.cron.pattern;
import cn.hutool.core.date.Month;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Assert;
import cn.hutool.cron.CronException;
/**
* 表达式各个部分的枚举,用于限定位置和规则<br>
* {@link #ordinal()}表示此部分在表达式中的位置如0表示秒
*
* @author looly
* @since 5.8.0
*/
public enum Part {
SECOND(0, 59),
MINUTE(0, 59),
HOUR(0, 23),
DAY_OF_MONTH(1, 31),
MONTH(Month.JANUARY.getValueBaseOne(), Month.DECEMBER.getValueBaseOne()),
DAY_OF_WEEK(Week.SUNDAY.ordinal(), Week.SATURDAY.ordinal()),
YEAR(1970, 2099);
private final int min;
private final int max;
/**
* 构造
*
* @param min 限定最小值(包含)
* @param max 限定最大值(包含)
*/
Part(int min, int max) {
if (min > max) {
this.min = max;
this.max = min;
} else {
this.min = min;
this.max = max;
}
}
/**
* 获取最小值
*
* @return 最小值
*/
public int getMin() {
return this.min;
}
/**
* 获取最大值
*
* @return 最大值
*/
public int getMax() {
return this.max;
}
/**
* 检查单个值是否有效
*
* @param value 值
* @return 检查后的值
* @throws CronException 检查无效抛出此异常
*/
public int checkValue(int value) throws CronException {
Assert.checkBetween(value, min, max,
() -> new CronException("Value {} out of range: [{} , {}]", value, min, max));
return value;
}
}

View File

@@ -31,8 +31,8 @@ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher {
*/
public boolean match(int value, int month, boolean isLeapYear) {
return (super.match(value) // 在约定日范围内的某一天
//匹配器中用户定义了最后一天32表示最后一天)
|| (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
//匹配器中用户定义了最后一天31表示最后一天)
|| (value > 27 && match(31) && isLastDayOfMonth(value, month, isLeapYear)));
}
/**

View File

@@ -1,10 +1,13 @@
package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.mutable.MutableBool;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
/**
* 时间匹配表,用于存放定时任务表达式解析后的结构信息
@@ -28,54 +31,107 @@ public class MatcherTable {
matchers = new ArrayList<>(size);
}
public LocalDateTime nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
List<LocalDateTime> nextMatchs = new ArrayList<>(second);
/**
* 获取下一个最近的匹配日期时间
*
* @param second 秒
* @param minute 分
* @param hour 时
* @param dayOfMonth 天
* @param month 月从0开始
* @param dayOfWeek 周
* @param year 年
* @param zone 时区
* @return {@link Calendar}
*/
public Calendar nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
List<Calendar> nextMatchs = new ArrayList<>(second);
for (DateTimeMatcher matcher : matchers) {
nextMatchs.add(singleNextMatchAfter(matcher, second, minute, hour,
dayOfMonth, month, dayOfWeek, year));
dayOfMonth, month, dayOfWeek, year, zone));
}
// 返回最先匹配到的日期
// 返回匹配到的最早日期
return CollUtil.min(nextMatchs);
}
private static LocalDateTime singleNextMatchAfter(DateTimeMatcher matcher, int second, int minute, int hour,
int dayOfMonth, int month, int dayOfWeek, int year){
boolean isNextNotEquals = true;
/**
* 获取下一个匹配日期时间
*
* @param matcher 匹配器
* @param second 秒
* @param minute 分
* @param hour 时
* @param dayOfMonth 天
* @param month 月从0开始
* @param dayOfWeek 周
* @param year 年
* @param zone 时区
* @return {@link Calendar}
*/
private static Calendar singleNextMatchAfter(DateTimeMatcher matcher, int second, int minute, int hour,
int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
Calendar calendar = Calendar.getInstance(zone);
// 上一个字段不一致,说明产生了新值,下一个字段使用最小值
MutableBool isNextEquals = new MutableBool(true);
// 年
final int nextYear = matcher.yearMatcher.nextAfter(year);
isNextNotEquals &= (year != nextYear);
final int nextYear = nextAfter(matcher.yearMatcher, year, isNextEquals);
calendar.set(Calendar.YEAR, nextYear);
// 周
int nextDayOfWeek;
if(isNextNotEquals){
// 上一个字段不一致,说明产生了新值,本字段使用最小值
nextDayOfWeek = ((BoolArrayValueMatcher)matcher.dayOfWeekMatcher).getMinValue();
}else{
nextDayOfWeek = matcher.dayOfWeekMatcher.nextAfter(dayOfWeek);
isNextNotEquals &= (dayOfWeek != nextDayOfWeek);
}
final int nextDayOfWeek = nextAfter(matcher.dayOfWeekMatcher, dayOfWeek, isNextEquals);
calendar.set(Calendar.DAY_OF_WEEK, nextDayOfWeek);
// 月
int nextMonth;
if(isNextNotEquals){
// 上一个字段不一致,说明产生了新值,本字段使用最小值
nextMonth = ((BoolArrayValueMatcher)matcher.monthMatcher).getMinValue();
}else{
nextMonth = matcher.monthMatcher.nextAfter(dayOfWeek);
isNextNotEquals &= (month != nextMonth);
}
final int nextMonth = nextAfter(matcher.monthMatcher, month + 1, isNextEquals);
calendar.set(Calendar.MONTH, nextMonth - 1);
// 日
int nextDayOfMonth;
if(isNextNotEquals){
// 上一个字段不一致,说明产生了新值,本字段使用最小值
nextDayOfMonth = ((BoolArrayValueMatcher)matcher.dayOfMonthMatcher).getMinValue();
}else{
nextDayOfMonth = matcher.dayOfMonthMatcher.nextAfter(dayOfWeek);
isNextNotEquals &= (dayOfMonth != nextDayOfMonth);
}
final int nextDayOfMonth = nextAfter(matcher.dayOfMonthMatcher, dayOfMonth, isNextEquals);
calendar.set(Calendar.DAY_OF_MONTH, nextDayOfMonth);
return null;
// 时
final int nextHour = nextAfter(matcher.hourMatcher, hour, isNextEquals);
calendar.set(Calendar.HOUR, nextHour);
// 分
int nextMinute = nextAfter(matcher.minuteMatcher, minute, isNextEquals);
calendar.set(Calendar.MINUTE, nextMinute);
// 秒
final int nextSecond = nextAfter(matcher.secondMatcher, second, isNextEquals);
calendar.set(Calendar.SECOND, nextSecond);
Console.log(nextYear, nextDayOfWeek, nextMonth, nextDayOfMonth, nextHour, nextMinute, nextSecond);
return calendar;
}
/**
* 获取对应字段匹配器的下一个值
*
* @param matcher 匹配器
* @param value 值
* @param isNextEquals 是否下一个值和值相同。不同获取初始值,相同获取下一值,然后修改。
* @return 下一个值,-1标识匹配所有值的情况应获取整个字段的最小值
*/
private static int nextAfter(ValueMatcher matcher, int value, MutableBool isNextEquals) {
int nextValue;
if (isNextEquals.get()) {
// 上一层级得到相同值,下级获取下个值
nextValue = matcher.nextAfter(value);
isNextEquals.set(nextValue == value);
} else {
// 上一层级的值得到了不同值,下级的所有值使用最小值
if (matcher instanceof AlwaysTrueValueMatcher) {
nextValue = value;
} else if (matcher instanceof BoolArrayValueMatcher) {
nextValue = ((BoolArrayValueMatcher) matcher).getMinValue();
} else {
throw new IllegalArgumentException("Invalid matcher: " + matcher.getClass().getName());
}
}
return nextValue;
}
/**
@@ -85,8 +141,8 @@ public class MatcherTable {
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth 天
* @param month 月
* @param dayOfWeek 周
* @param month 月从1开始
* @param dayOfWeek 周从0开始0和7都表示周日
* @param year 年
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/

View File

@@ -1,39 +0,0 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import java.util.List;
/**
* 每月的几号值处理<br>
* 每月最多31天32和“L”都表示最后一天
*
* @author Looly
*
*/
public class DayOfMonthValueParser extends AbsValueParser {
/**
* 构造
*/
public DayOfMonthValueParser() {
super(1, 31);
}
@Override
public int parse(String value) throws CronException {
if ("L".equalsIgnoreCase(value) || "32".equals(value)) {// 每月最后一天
return 32;
} else {
return super.parse(value);
}
}
@Override
protected ValueMatcher buildValueMatcher(List<Integer> values) {
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
return new DayOfMonthValueMatcher(values);
}
}

View File

@@ -1,56 +0,0 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
/**
* 星期值处理<br>
* 1表示星期一2表示星期二依次类推0和7都可以表示星期日<br>
* {@code L}表示周六
*
* @author Looly
*/
public class DayOfWeekValueParser extends AbsValueParser {
/**
* Weeks aliases.
*/
private static final String[] ALIASES = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"};
public DayOfWeekValueParser() {
super(0, 7);
}
/**
* 对于星期提供转换<br>
* 1表示星期一2表示星期二依次类推0和7都可以表示星期日
*/
@Override
public int parse(String value) throws CronException {
try {
return super.parse(value) % 7;
} catch (Exception e) {
return parseAlias(value);
}
}
/**
* 解析别名
*
* @param value 别名值
* @return 月份int值
* @throws CronException 无效别名抛出此异常
*/
private int parseAlias(String value) throws CronException {
if ("L".equalsIgnoreCase(value)) {
//最后一天为星期六
return ALIASES.length - 1;
}
for (int i = 0; i < ALIASES.length; i++) {
if (ALIASES[i].equalsIgnoreCase(value)) {
return i;
}
}
throw new CronException("Invalid month alias: {}", value);
}
}

View File

@@ -1,13 +0,0 @@
package cn.hutool.cron.pattern.parser;
/**
* 小时值处理<br>
* 小时被限定在0-23
*
* @author Looly
*/
public class HourValueParser extends AbsValueParser {
public HourValueParser() {
super(0, 23);
}
}

View File

@@ -1,16 +0,0 @@
package cn.hutool.cron.pattern.parser;
/**
* 分钟值处理<br>
* 限定于0-59
*
* @author Looly
*/
public class MinuteValueParser extends AbsValueParser {
/**
* 构造
*/
public MinuteValueParser() {
super(0, 59);
}
}

View File

@@ -1,40 +0,0 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.core.date.Month;
import cn.hutool.core.lang.Assert;
import cn.hutool.cron.CronException;
/**
* 月份值处理<br>
* 限定于1-121表示一月支持别名忽略大小写如一月是{@code jan}
*
* @author Looly
*/
public class MonthValueParser extends AbsValueParser {
public MonthValueParser() {
super(1, 12);
}
@Override
public int parse(String value) throws CronException {
try {
return super.parse(value);
} catch (Exception e) {
return parseAlias(value);
}
}
/**
* 解析别名
*
* @param monthName 别名值
* @return 月份int值从1开始
* @throws CronException 无效月别名抛出此异常
*/
private int parseAlias(String monthName) throws CronException {
final Month month = Month.of(monthName);
Assert.notNull(month, () -> new CronException("Invalid month alias: {}", monthName));
return month.getValueBaseOne();
}
}

View File

@@ -1,109 +1,75 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.Month;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.Part;
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
import cn.hutool.cron.pattern.matcher.BoolArrayValueMatcher;
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
import java.util.ArrayList;
import java.util.List;
/**
* 简易值转换器将给定String值转为int并限定最大值和最小值<br>
* 此类同时识别{@code L} 为最大值
* 定时任务表达式各个部分的解析器
*
* @author Looly
* @author looly
* @since 5.8.0
*/
public abstract class AbsValueParser implements ValueParser {
public class PartParser {
private final Part part;
/**
* 最小值包括
* 创建解析器
*
* @param part 对应解析的部分枚举
* @return 解析器
*/
protected int min;
/**
* 最大值包括
*/
protected int max;
public static PartParser of(Part part) {
return new PartParser(part);
}
/**
* 构造
*
* @param min 最小值包括
* @param max 最大值包括
* @param part 对应解析的部分枚举
*/
public AbsValueParser(int min, int max) {
if (min > max) {
this.min = max;
this.max = min;
} else {
this.min = min;
this.max = max;
}
}
@Override
public int parse(String value) throws CronException {
if ("L".equalsIgnoreCase(value)) {
// L表示最大值
return max;
}
int i;
try {
i = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new CronException(e, "Invalid integer value: '{}'", value);
}
if (i < min || i > max) {
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
}
return i;
}
@Override
public int getMin() {
return this.min;
}
@Override
public int getMax() {
return this.max;
public PartParser(Part part) {
this.part = part;
}
/**
* 处理定时任务表达式每个时间字段<br>
* 多个时间使用逗号分隔
* 将表达式解析为{@link ValueMatcher}
*
* @param value 某个时间字段
* @return List
* @param value 表达式
* @return {@link ValueMatcher}
*/
@Override
public ValueMatcher parseAsValueMatcher(String value) {
if (isMatchAllStr(value)) {
//兼容Quartz的"?"表达式不会出现互斥情况"*"作用相同
return new AlwaysTrueValueMatcher();
}
List<Integer> values = parseArray(value);
final List<Integer> values = parseArray(value);
if (values.size() == 0) {
throw new CronException("Invalid field: [{}]", value);
throw new CronException("Invalid part value: [{}]", value);
}
return buildValueMatcher(values);
}
/**
* 根据解析的数字值列表构建{@link ValueMatcher}<br>
* 默认为{@link BoolArrayValueMatcher}如果有特殊实现子类须重写此方法
*
* @param values 数字值列表
* @return {@link ValueMatcher}
*/
protected ValueMatcher buildValueMatcher(List<Integer> values){
return new BoolArrayValueMatcher(values);
switch (this.part) {
case DAY_OF_MONTH:
return new DayOfMonthValueMatcher(values);
case YEAR:
return new YearValueMatcher(values);
default:
return new BoolArrayValueMatcher(values);
}
}
/**
@@ -113,10 +79,11 @@ public abstract class AbsValueParser implements ValueParser {
* <li><strong>a</strong> <strong>*</strong></li>
* <li><strong>a,b,c,d</strong></li>
* </ul>
*
* @param value 子表达式值
* @return 值列表
*/
private List<Integer> parseArray(String value){
private List<Integer> parseArray(String value) {
final List<Integer> values = new ArrayList<>();
final List<String> parts = StrUtil.split(value, StrUtil.C_COMMA);
@@ -146,7 +113,7 @@ public abstract class AbsValueParser implements ValueParser {
if (size == 1) {// 普通形式
results = parseRange(value, -1);
} else if (size == 2) {// 间隔形式
final int step = parse(parts.get(1));
final int step = parseNumber(parts.get(1));
if (step < 1) {
throw new CronException("Non positive divisor for field: [{}]", value);
}
@@ -168,7 +135,7 @@ public abstract class AbsValueParser implements ValueParser {
* </ul>
*
* @param value 范围表达式
* @param step 步进
* @param step 步进
* @return List
*/
private List<Integer> parseRange(String value, int step) {
@@ -177,22 +144,22 @@ public abstract class AbsValueParser implements ValueParser {
// 全部匹配形式
if (value.length() <= 2) {
//根据步进的第一个数字确定起始时间类似于 12/3则从12分等开始
int minValue = getMin();
if(false == isMatchAllStr(value)) {
minValue = Math.max(minValue, parse(value));
}else {
int minValue = part.getMin();
if (false == isMatchAllStr(value)) {
minValue = Math.max(minValue, parseNumber(value));
} else {
//在全匹配模式下如果步进不存在表示步进为1
if(step < 1) {
if (step < 1) {
step = 1;
}
}
if(step > 0) {
final int maxValue = getMax();
if(minValue > maxValue) {
if (step > 0) {
final int maxValue = part.getMax();
if (minValue > maxValue) {
throw new CronException("Invalid value {} > {}", minValue, maxValue);
}
//有步进
for (int i = minValue; i <= maxValue; i+=step) {
for (int i = minValue; i <= maxValue; i += step) {
results.add(i);
}
} else {
@@ -206,26 +173,26 @@ public abstract class AbsValueParser implements ValueParser {
List<String> parts = StrUtil.split(value, '-');
int size = parts.size();
if (size == 1) {// 普通值
final int v1 = parse(value);
if(step > 0) {//类似 20/2的形式
NumberUtil.appendRange(v1, getMax(), step, results);
}else {
final int v1 = parseNumber(value);
if (step > 0) {//类似 20/2的形式
NumberUtil.appendRange(v1, part.getMax(), step, results);
} else {
results.add(v1);
}
} else if (size == 2) {// range值
final int v1 = parse(parts.get(0));
final int v2 = parse(parts.get(1));
if(step < 1) {
final int v1 = parseNumber(parts.get(0));
final int v2 = parseNumber(parts.get(1));
if (step < 1) {
//在range模式下如果步进不存在表示步进为1
step = 1;
}
if (v1 < v2) {// 正常范围例如2-5
NumberUtil.appendRange(v1, v2, step, results);
} else if (v1 > v2) {// 逆向范围反选模式例如5-2
NumberUtil.appendRange(v1, getMax(), step, results);
NumberUtil.appendRange(getMin(), v2, step, results);
NumberUtil.appendRange(v1, part.getMax(), step, results);
NumberUtil.appendRange(part.getMin(), v2, step, results);
} else {// v1 == v2此时与单值模式一致
NumberUtil.appendRange(v1, getMax(), step, results);
NumberUtil.appendRange(v1, part.getMax(), step, results);
}
} else {
throw new CronException("Invalid syntax of field: [{}]", value);
@@ -244,4 +211,56 @@ public abstract class AbsValueParser implements ValueParser {
private static boolean isMatchAllStr(String value) {
return (1 == value.length()) && ("*".equals(value) || "?".equals(value));
}
/**
* 解析单个int值支持别名
*
* @param value 被解析的值
* @return 解析结果
* @throws CronException 当无效数字或无效别名时抛出
*/
private int parseNumber(String value) throws CronException {
if ("L".equalsIgnoreCase(value)) {
// L表示最大值
return part.getMax();
}
int i;
try {
i = Integer.parseInt(value);
} catch (NumberFormatException ignore) {
i = parseAlias(value);
}
// 周日可以用0或7表示统一转换为0
if(this.part == Part.DAY_OF_WEEK && Week.SUNDAY.getIso8601Value() == i){
i = Week.SUNDAY.ordinal();
}
return part.checkValue(i);
}
/**
* 解析别名只支持{@link Part#MONTH}{@link Part#DAY_OF_WEEK}
*
* @param name 别名
* @return 解析int值
* @throws CronException 无匹配别名时抛出异常
*/
private int parseAlias(String name) throws CronException {
switch (this.part) {
case MONTH:
final Month month = Month.of(name);
Assert.notNull(month, () -> new CronException("Invalid month alias: {}", name));
// 月份从1开始
return month.getValueBaseOne();
case DAY_OF_WEEK:
final Week week = Week.of(name);
Assert.notNull(week, () -> new CronException("Invalid day of week alias: {}", name));
// 周从0开始0表示周日
return week.ordinal();
}
throw new CronException("Invalid alias value: [{}]", name);
}
}

View File

@@ -1,8 +1,9 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.Part;
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
import cn.hutool.cron.pattern.matcher.DateTimeMatcher;
import cn.hutool.cron.pattern.matcher.MatcherTable;
@@ -16,15 +17,15 @@ import java.util.List;
* @author looly
* @since 5.8.0
*/
public class CronPatternParser {
public class PatternParser {
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
private static final PartParser SECOND_VALUE_PARSER = PartParser.of(Part.SECOND);
private static final PartParser MINUTE_VALUE_PARSER = PartParser.of(Part.MINUTE);
private static final PartParser HOUR_VALUE_PARSER = PartParser.of(Part.HOUR);
private static final PartParser DAY_OF_MONTH_VALUE_PARSER = PartParser.of(Part.DAY_OF_MONTH);
private static final PartParser MONTH_VALUE_PARSER = PartParser.of(Part.MONTH);
private static final PartParser DAY_OF_WEEK_VALUE_PARSER = PartParser.of(Part.DAY_OF_WEEK);
private static final PartParser YEAR_VALUE_PARSER = PartParser.of(Part.YEAR);
/**
* 解析表达式到匹配表中
@@ -46,7 +47,7 @@ public class CronPatternParser {
* @return {@link MatcherTable}
*/
private static MatcherTable parseGroupPattern(String groupPattern) {
final List<String> patternList = StrUtil.split(groupPattern, '|');
final List<String> patternList = StrUtil.splitTrim(groupPattern, '|');
final MatcherTable matcherTable = new MatcherTable(patternList.size());
for (String pattern : patternList) {
matcherTable.matchers.add(parseSinglePattern(pattern));
@@ -61,17 +62,18 @@ public class CronPatternParser {
* @return {@link DateTimeMatcher}
*/
private static DateTimeMatcher parseSinglePattern(String pattern) {
final String[] parts = pattern.split("\\s");
final String[] parts = pattern.split("\\s+");
Assert.checkBetween(parts.length, 5, 7,
() -> new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern));
int offset = 0;// 偏移量用于兼容Quartz表达式当表达式有6或7项时第一项为秒
// 偏移量用于兼容Quartz表达式当表达式有6或7项时第一项为秒
int offset = 0;
if (parts.length == 6 || parts.length == 7) {
offset = 1;
} else if (parts.length != 5) {
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
}
// 如果不支持秒的表达式则第一位按照表达式生成时间的秒数赋值表示整分匹配
final String secondPart = (1 == offset) ? parts[0] : String.valueOf(DateUtil.date().second());
// 如果不支持秒的表达式则第一位赋值0表示整分匹配
final String secondPart = (1 == offset) ? parts[0] : "0";
//
ValueMatcher yearMatcher;

View File

@@ -1,10 +0,0 @@
package cn.hutool.cron.pattern.parser;
/**
* 秒值处理<br>
* 限定于0-59
*
* @author Looly
*/
public class SecondValueParser extends MinuteValueParser {
}

View File

@@ -1,53 +0,0 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
/**
* 值处理接口<br>
* 值处理用于限定表达式中相应位置的值范围并转换表达式值为int值
*
* @author Looly
*/
public interface ValueParser {
/**
* 解析表达式对应部分为{@link ValueMatcher},支持的表达式包括:
* <ul>
* <li>单值或通配符形式,如 <strong>a</strong> 或 <strong>*</strong></li>
* <li>数组形式,如 <strong>1,2,3</strong></li>
* <li>间隔形式,如 <strong>a&#47;b</strong> 或 <strong>*&#47;b</strong></li>
* <li>范围形式,如 <strong>3-8</strong></li>
* </ul>
*
* @param pattern 对应时间部分的表达式
* @return {@link ValueMatcher}
*/
ValueMatcher parseAsValueMatcher(String pattern);
/**
* 处理String值并转为int<br>
* 转换包括:
* <ol>
* <li>数字字符串转为数字</li>
* <li>别名转为对应的数字(如月份和星期)</li>
* </ol>
*
* @param value String值
* @return int
*/
int parse(String value);
/**
* 返回最小值
*
* @return 最小值
*/
int getMin();
/**
* 返回最大值
*
* @return 最大值
*/
int getMax();
}

View File

@@ -1,25 +0,0 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
import java.util.List;
/**
* 年值处理<br>
* 年的限定在1970-2099年
*
* @author Looly
*/
public class YearValueParser extends AbsValueParser {
public YearValueParser() {
super(1970, 2099);
}
@Override
protected ValueMatcher buildValueMatcher(List<Integer> values) {
//考虑年数字太大不适合boolean数组单独使用列表遍历匹配
return new YearValueMatcher(values);
}
}