This commit is contained in:
Looly 2022-03-22 23:59:57 +08:00
parent 259fa3f739
commit d1122bd3c5
16 changed files with 185 additions and 218 deletions

View File

@ -75,7 +75,7 @@ public class CronPattern {
*/
public CronPattern(String pattern) {
this.pattern = pattern;
this.matcherTable = CronPatternParser.create().parse(pattern);
this.matcherTable = CronPatternParser.parse(pattern);
}
// --------------------------------------------------------------------------------------- match start

View File

@ -3,17 +3,24 @@ package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.util.StrUtil;
/**
* 值匹配始终返回<code>true</code>
* @author Looly
* 所有值匹配始终返回{@code true}
*
* @author Looly
*/
public class AlwaysTrueValueMatcher implements ValueMatcher{
public class AlwaysTrueValueMatcher implements ValueMatcher {
public static AlwaysTrueValueMatcher INSTANCE = new AlwaysTrueValueMatcher();
@Override
public boolean match(Integer t) {
return true;
}
@Override
public int nextAfter(int value) {
return value;
}
@Override
public String toString() {
return StrUtil.format("[Matcher]: always true.");

View File

@ -44,6 +44,23 @@ public class BoolArrayValueMatcher implements ValueMatcher {
return bValues[value];
}
@Override
public int nextAfter(int value) {
if(value > minValue){
while(value < bValues.length){
if(bValues[value]){
return value;
}
value++;
}
}
// 两种情况返回最小值
// 一是给定值小于最小值那下一个匹配值就是最小值
// 二是给定值大于最大值那下一个匹配值也是下一轮的最小值
return minValue;
}
/**
* 获取表达式定义的最小值
*

View File

@ -0,0 +1,95 @@
package cn.hutool.cron.pattern.matcher;
import java.time.Year;
/**
* 日期和时间的单一匹配器
*
* @author looly
* @since 5.8.0
*/
public class DateTimeMatcher {
/**
* 秒字段匹配列表
*/
final ValueMatcher secondMatcher;
/**
* 分字段匹配列表
*/
final ValueMatcher minuteMatcher;
/**
* 时字段匹配列表
*/
final ValueMatcher hourMatcher;
/**
* 每月几号字段匹配列表
*/
final ValueMatcher dayOfMonthMatcher;
/**
* 月字段匹配列表
*/
final ValueMatcher monthMatcher;
/**
* 星期字段匹配列表
*/
final ValueMatcher dayOfWeekMatcher;
/**
* 年字段匹配列表
*/
final ValueMatcher yearMatcher;
public DateTimeMatcher(ValueMatcher secondMatcher,
ValueMatcher minuteMatcher,
ValueMatcher hourMatchers,
ValueMatcher dayOfMonthMatchers,
ValueMatcher monthMatchers,
ValueMatcher dayOfWeekMatchers,
ValueMatcher yearMatchers) {
this.secondMatcher = secondMatcher;
this.minuteMatcher = minuteMatcher;
this.hourMatcher = hourMatchers;
this.dayOfMonthMatcher = dayOfMonthMatchers;
this.monthMatcher = monthMatchers;
this.dayOfWeekMatcher = dayOfWeekMatchers;
this.yearMatcher = yearMatchers;
}
/**
* 给定时间是否匹配定时任务表达式
*
* @param second 秒数-1表示不匹配此项
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth
* @param month
* @param dayOfWeek 周几
* @param year
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
return ((second < 0) || secondMatcher.match(second)) // 匹配秒非秒匹配模式下始终返回true
&& minuteMatcher.match(minute)// 匹配分
&& hourMatcher.match(hour)// 匹配时
&& isMatchDayOfMonth(dayOfMonthMatcher, dayOfMonth, month, Year.isLeap(year))// 匹配日
&& monthMatcher.match(month) // 匹配月
&& dayOfWeekMatcher.match(dayOfWeek)// 匹配周
&& yearMatcher.match(year);// 匹配年
}
/**
* 是否匹配日指定月份的第几天
*
* @param matcher {@link ValueMatcher}
* @param dayOfMonth
* @param month
* @param isLeapYear 是否闰年
* @return 是否匹配
*/
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
return ((matcher instanceof DayOfMonthValueMatcher) //
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
: matcher.match(dayOfMonth));
}
}

View File

@ -1,6 +1,6 @@
package cn.hutool.cron.pattern.matcher;
import java.time.Year;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ -12,38 +12,10 @@ import java.util.List;
*/
public class MatcherTable {
/**
* 匹配器个数取决于复合任务表达式中的单一表达式个数
*/
public int matcherSize;
/**
* 秒字段匹配列表
*/
public final List<ValueMatcher> secondMatchers;
/**
* 分字段匹配列表
*/
public final List<ValueMatcher> minuteMatchers;
/**
* 时字段匹配列表
*/
public final List<ValueMatcher> hourMatchers;
/**
* 每月几号字段匹配列表
*/
public final List<ValueMatcher> dayOfMonthMatchers;
/**
* 月字段匹配列表
*/
public final List<ValueMatcher> monthMatchers;
/**
* 星期字段匹配列表
*/
public final List<ValueMatcher> dayOfWeekMatchers;
/**
* 年字段匹配列表
*/
public final List<ValueMatcher> yearMatchers;
public final List<DateTimeMatcher> matchers;
/**
* 构造
@ -51,14 +23,11 @@ public class MatcherTable {
* @param size 表达式个数用于表示复合表达式中单个表达式个数
*/
public MatcherTable(int size) {
matcherSize = size;
secondMatchers = new ArrayList<>(size);
minuteMatchers = new ArrayList<>(size);
hourMatchers = new ArrayList<>(size);
dayOfMonthMatchers = new ArrayList<>(size);
monthMatchers = new ArrayList<>(size);
dayOfWeekMatchers = new ArrayList<>(size);
yearMatchers = new ArrayList<>(size);
matchers = new ArrayList<>(size);
}
public LocalDateTime nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
return null;
}
/**
@ -74,46 +43,11 @@ public class MatcherTable {
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
for (int i = 0; i < matcherSize; i++) {
boolean eval = ((second < 0) || secondMatchers.get(i).match(second)) // 匹配秒非秒匹配模式下始终返回true
&& minuteMatchers.get(i).match(minute)// 匹配分
&& hourMatchers.get(i).match(hour)// 匹配时
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, Year.isLeap(year))// 匹配日
&& monthMatchers.get(i).match(month) // 匹配月
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
&& isMatch(yearMatchers, i, year);// 匹配年
if (eval) {
for (DateTimeMatcher matcher : matchers) {
if (matcher.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year)) {
return true;
}
}
return false;
}
/**
* 是否匹配日指定月份的第几天
*
* @param matcher {@link ValueMatcher}
* @param dayOfMonth
* @param month
* @param isLeapYear 是否闰年
* @return 是否匹配
*/
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
return ((matcher instanceof DayOfMonthValueMatcher) //
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
: matcher.match(dayOfMonth));
}
/**
* 是否匹配指定的日期时间位置
*
* @param matchers 匹配器列表
* @param index 位置
* @param value 被匹配的值
* @return 是否匹配
* @since 4.0.2
*/
private static boolean isMatch(List<ValueMatcher> matchers, int index, int value) {
return (matchers.size() <= index) || matchers.get(index).match(value);
}
}

View File

@ -5,9 +5,14 @@ import cn.hutool.core.lang.Matcher;
/**
* 值匹配器<br>
* 用于匹配日期位中对应数字是否匹配
* @author Looly
*
* @author Looly
*/
public interface ValueMatcher extends Matcher<Integer>{
public interface ValueMatcher extends Matcher<Integer> {
/**
* 获取指定值之后的匹配值也可以是指定值本身
* @param value 指定的值
* @return 匹配到的值或之后的值
*/
int nextAfter(int value);
}

View File

@ -9,9 +9,9 @@ import java.util.List;
*
*/
public class YearValueMatcher implements ValueMatcher{
private final List<Integer> valueList;
public YearValueMatcher(List<Integer> intValueList) {
this.valueList = intValueList;
}
@ -20,4 +20,16 @@ public class YearValueMatcher implements ValueMatcher{
public boolean match(Integer t) {
return valueList.contains(t);
}
@Override
public int nextAfter(int value) {
for (Integer year : valueList) {
if(year >= value){
return year;
}
}
// 年无效此表达式整体无效
return -1;
}
}

View File

@ -4,7 +4,9 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
import cn.hutool.cron.pattern.matcher.DateTimeMatcher;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import java.util.List;
@ -24,26 +26,14 @@ public class CronPatternParser {
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
/**
* 创建表达式解析器
*
* @return CronPatternParser
*/
public static CronPatternParser create() {
return new CronPatternParser();
}
private MatcherTable matcherTable;
/**
* 解析表达式到匹配表中
*
* @param cronPattern 复合表达式
* @return {@link MatcherTable}
*/
public MatcherTable parse(String cronPattern) {
parseGroupPattern(cronPattern);
return this.matcherTable;
public static MatcherTable parse(String cronPattern) {
return parseGroupPattern(cronPattern);
}
/**
@ -53,21 +43,24 @@ public class CronPatternParser {
* </pre>
*
* @param groupPattern 复合表达式
* @return {@link MatcherTable}
*/
private void parseGroupPattern(String groupPattern) {
private static MatcherTable parseGroupPattern(String groupPattern) {
final List<String> patternList = StrUtil.split(groupPattern, '|');
matcherTable = new MatcherTable(patternList.size());
final MatcherTable matcherTable = new MatcherTable(patternList.size());
for (String pattern : patternList) {
parseSinglePattern(pattern);
matcherTable.matchers.add(parseSinglePattern(pattern));
}
return matcherTable;
}
/**
* 解析单一定时任务表达式
*
* @param pattern 表达式
* @return {@link DateTimeMatcher}
*/
private void parseSinglePattern(String pattern) {
private static DateTimeMatcher parseSinglePattern(String pattern) {
final String[] parts = pattern.split("\\s");
int offset = 0;// 偏移量用于兼容Quartz表达式当表达式有6或7项时第一项为秒
@ -79,38 +72,30 @@ public class CronPatternParser {
// 如果不支持秒的表达式则第一位按照表达式生成时间的秒数赋值表示整分匹配
final String secondPart = (1 == offset) ? parts[0] : String.valueOf(DateUtil.date().second());
parseToTable(SECOND_VALUE_PARSER, secondPart);
//
parseToTable(MINUTE_VALUE_PARSER, parts[offset]);
//
parseToTable(HOUR_VALUE_PARSER, parts[1 + offset]);
//
parseToTable(DAY_OF_MONTH_VALUE_PARSER, parts[2 + offset]);
//
parseToTable(MONTH_VALUE_PARSER, parts[3 + offset]);
//
parseToTable(DAY_OF_WEEK_VALUE_PARSER, parts[4 + offset]);
//
ValueMatcher yearMatcher;
if (parts.length == 7) {// 支持年的表达式
parseToTable(YEAR_VALUE_PARSER, parts[6]);
yearMatcher = YEAR_VALUE_PARSER.parseAsValueMatcher(parts[6]);
} else {// 不支持年的表达式全部匹配
matcherTable.yearMatchers.add(new AlwaysTrueValueMatcher());
yearMatcher = AlwaysTrueValueMatcher.INSTANCE;
}
}
/**
* 将表达式解析后加入到{@link #matcherTable}
*
* @param valueParser 表达式解析器
* @param patternPart 表达式部分
*/
private void parseToTable(ValueParser valueParser, String patternPart) {
valueParser.parseTo(this.matcherTable, patternPart);
return new DateTimeMatcher(
//
SECOND_VALUE_PARSER.parseAsValueMatcher(secondPart),
//
MINUTE_VALUE_PARSER.parseAsValueMatcher(parts[offset]),
//
HOUR_VALUE_PARSER.parseAsValueMatcher(parts[1 + offset]),
//
DAY_OF_MONTH_VALUE_PARSER.parseAsValueMatcher(parts[2 + offset]),
//
MONTH_VALUE_PARSER.parseAsValueMatcher(parts[3 + offset]),
//
DAY_OF_WEEK_VALUE_PARSER.parseAsValueMatcher(parts[4 + offset]),
//
yearMatcher
);
}
}

View File

@ -2,7 +2,6 @@ package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import java.util.List;
@ -32,15 +31,6 @@ public class DayOfMonthValueParser extends AbsValueParser {
}
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.dayOfMonthMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
}
}
@Override
protected ValueMatcher buildValueMatcher(List<Integer> values) {
//考虑每月的天数不同且存在闰年情况日匹配单独使用

View File

@ -1,7 +1,6 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
/**
* 星期值处理<br>
@ -34,15 +33,6 @@ public class DayOfWeekValueParser extends AbsValueParser {
}
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.dayOfWeekMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
}
}
/**
* 解析别名
*

View File

@ -1,8 +1,5 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
/**
* 小时值处理<br>
* 小时被限定在0-23
@ -10,17 +7,7 @@ import cn.hutool.cron.pattern.matcher.MatcherTable;
* @author Looly
*/
public class HourValueParser extends AbsValueParser {
public HourValueParser() {
super(0, 23);
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.hourMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
}
}
}

View File

@ -1,8 +1,5 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
/**
* 分钟值处理<br>
* 限定于0-59
@ -10,20 +7,10 @@ import cn.hutool.cron.pattern.matcher.MatcherTable;
* @author Looly
*/
public class MinuteValueParser extends AbsValueParser {
/**
* 构造
*/
public MinuteValueParser() {
super(0, 59);
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.minuteMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
}
}
}

View File

@ -1,7 +1,6 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
/**
* 月份值处理<br>
@ -29,15 +28,6 @@ public class MonthValueParser extends AbsValueParser {
}
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.monthMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
}
}
/**
* 解析别名
*

View File

@ -1,8 +1,5 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
/**
* 秒值处理<br>
* 限定于0-59
@ -10,13 +7,4 @@ import cn.hutool.cron.pattern.matcher.MatcherTable;
* @author Looly
*/
public class SecondValueParser extends MinuteValueParser {
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.secondMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
}
}
}

View File

@ -1,6 +1,5 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
/**
@ -11,14 +10,6 @@ import cn.hutool.cron.pattern.matcher.ValueMatcher;
*/
public interface ValueParser {
/**
* 解析表达式后加入到{@link MatcherTable}的对应列表中
*
* @param matcherTable {@link MatcherTable}
* @param pattern 对应时间部分的表达式
*/
void parseTo(MatcherTable matcherTable, String pattern);
/**
* 解析表达式对应部分为{@link ValueMatcher}支持的表达式包括
* <ul>

View File

@ -1,7 +1,5 @@
package cn.hutool.cron.pattern.parser;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
@ -19,15 +17,6 @@ public class YearValueParser extends AbsValueParser {
super(1970, 2099);
}
@Override
public void parseTo(MatcherTable matcherTable, String pattern) {
try {
matcherTable.yearMatchers.add(parseAsValueMatcher(pattern));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
}
}
@Override
protected ValueMatcher buildValueMatcher(List<Integer> values) {
//考虑年数字太大不适合boolean数组单独使用列表遍历匹配