add class

This commit is contained in:
Looly 2022-03-21 00:42:01 +08:00
parent be72eab9a0
commit 4fbda4565a
6 changed files with 270 additions and 179 deletions

View File

@ -33,6 +33,7 @@
* 【poi 】 优化ExcelBase将alias放入
* 【poi 】 优化ExcelBase将alias放入
* 【core 】 改进StrUtil#startWith、endWith性能
* 【cron 】 增加CronPatternParser、MatcherTable
### 🐞Bug修复
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题pr#555@Gitee

View File

@ -1756,7 +1756,7 @@ public class DateUtil extends CalendarUtil {
* @return 是否闰年
*/
public static boolean isLeapYear(int year) {
return new GregorianCalendar().isLeapYear(year);
return Year.isLeap(year);
}
/**

View File

@ -1,25 +1,10 @@
package cn.hutool.cron.pattern;
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.DayOfMonthValueMatcher;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.ValueMatcherBuilder;
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
import cn.hutool.cron.pattern.parser.DayOfWeekValueParser;
import cn.hutool.cron.pattern.parser.HourValueParser;
import cn.hutool.cron.pattern.parser.MinuteValueParser;
import cn.hutool.cron.pattern.parser.MonthValueParser;
import cn.hutool.cron.pattern.parser.SecondValueParser;
import cn.hutool.cron.pattern.parser.ValueParser;
import cn.hutool.cron.pattern.parser.YearValueParser;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.parser.CronPatternParser;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
/**
@ -32,14 +17,14 @@ import java.util.TimeZone;
* <li><strong></strong> 范围1~12同时支持不区分大小写的别名"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
* <li><strong></strong> 范围0 (Sunday)~6(Saturday)7也可以表示周日同时支持不区分大小写的别名"sun","mon", "tue", "wed", "thu","fri", "sat"<strong>"L"</strong> 表示周六</li>
* </ol>
*
* <p>
* 为了兼容Quartz表达式同时支持6位和7位表达式其中<br>
*
* <pre>
* 当为6位时第一位表示<strong></strong> 范围0~59但是第一位不做匹配
* 当为7位时最后一位表示<strong></strong> 范围1970~2099但是第7位不做解析也不做匹配
* </pre>
*
* <p>
* 当定时任务运行到的时间匹配这些表达式后任务被启动<br>
* 注意
*
@ -47,7 +32,7 @@ import java.util.TimeZone;
* 当isMatchSecond为{@code true}时才会匹配秒部分
* 默认都是关闭的
* </pre>
*
* <p>
* 对于每一个子表达式同样支持以下形式
* <ul>
* <li><strong>*</strong> 表示匹配这个位置所有的时间</li>
@ -62,10 +47,10 @@ import java.util.TimeZone;
* <pre>
* 间隔/ &gt; 区间- &gt; 列表,
* </pre>
*
* <p>
* 例如 2,3,6/3中由于/优先级高因此相当于2,3,(6/3)结果与 2,3,6等价<br>
* <br>
*
* <p>
* 一些例子
* <ul>
* <li><strong>5 * * * *</strong> 每个点钟的5分执行00:05,01:05</li>
@ -77,36 +62,11 @@ import java.util.TimeZone;
* </ul>
*
* @author Looly
*
*/
public class CronPattern {
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 final String pattern;
/** 秒字段匹配列表 */
private final List<ValueMatcher> secondMatchers = new ArrayList<>();
/** 分字段匹配列表 */
private final List<ValueMatcher> minuteMatchers = new ArrayList<>();
/** 时字段匹配列表 */
private final List<ValueMatcher> hourMatchers = new ArrayList<>();
/** 每月几号字段匹配列表 */
private final List<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
/** 月字段匹配列表 */
private final List<ValueMatcher> monthMatchers = new ArrayList<>();
/** 星期字段匹配列表 */
private final List<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
/** 年字段匹配列表 */
private final List<ValueMatcher> yearMatchers = new ArrayList<>();
/** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */
private int matcherSize;
private final MatcherTable matcherTable;
/**
* 构造
@ -115,14 +75,15 @@ public class CronPattern {
*/
public CronPattern(String pattern) {
this.pattern = pattern;
parseGroupPattern(pattern);
this.matcherTable = CronPatternParser.create().parse(pattern);
}
// --------------------------------------------------------------------------------------- match start
/**
* 给定时间是否匹配定时任务表达式
*
* @param millis 时间毫秒数
* @param millis 时间毫秒数
* @param isMatchSecond 是否匹配秒
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
@ -133,8 +94,8 @@ public class CronPattern {
/**
* 给定时间是否匹配定时任务表达式
*
* @param timezone 时区 {@link TimeZone}
* @param millis 时间毫秒数
* @param timezone 时区 {@link TimeZone}
* @param millis 时间毫秒数
* @param isMatchSecond 是否匹配秒
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
@ -147,11 +108,12 @@ public class CronPattern {
/**
* 给定时间是否匹配定时任务表达式
*
* @param calendar 时间
* @param calendar 时间
* @param isMatchSecond 是否匹配秒
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(GregorianCalendar 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);
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
@ -159,20 +121,7 @@ public class CronPattern {
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始0和7都表示周日
final int year = calendar.get(Calendar.YEAR);
boolean eval;
for (int i = 0; i < matcherSize; i++) {
eval = ((false == isMatchSecond) || secondMatchers.get(i).match(calendar.get(Calendar.SECOND))) // 匹配秒非秒匹配模式下始终返回true
&& minuteMatchers.get(i).match(minute)// 匹配分
&& hourMatchers.get(i).match(hour)// 匹配时
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, calendar.isLeapYear(year))// 匹配日
&& monthMatchers.get(i).match(month) // 匹配月
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
&& isMatch(yearMatchers, i, year);// 匹配年
if (eval) {
return true;
}
}
return false;
return this.matcherTable.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year);
}
// --------------------------------------------------------------------------------------- match end
@ -180,114 +129,4 @@ public class CronPattern {
public String toString() {
return this.pattern;
}
// -------------------------------------------------------------------------------------- Private method start
/**
* 是否匹配日指定月份的第几天
*
* @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);
}
/**
* 解析复合任务表达式
*
* @param groupPattern 复合表达式
*/
private void parseGroupPattern(String groupPattern) {
List<String> patternList = StrUtil.split(groupPattern, '|');
for (String pattern : patternList) {
parseSinglePattern(pattern);
}
}
/**
* 解析单一定时任务表达式
*
* @param pattern 表达式
*/
private void parseSinglePattern(String pattern) {
final String[] parts = pattern.split("\\s");
int offset = 0;// 偏移量用于兼容Quartz表达式当表达式有6或7项时第一项为秒
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);
}
//
if (1 == offset) {// 支持秒的表达式
try {
this.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
}
} else {// 不支持秒的表达式则第一位按照表达式生成时间的秒数赋值表示整分匹配
this.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER));
}
//
try {
this.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
}
// 小时
try {
this.hourMatchers.add(ValueMatcherBuilder.build(parts[1 + offset], HOUR_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
}
// 每月第几天
try {
this.dayOfMonthMatchers.add(ValueMatcherBuilder.build(parts[2 + offset], DAY_OF_MONTH_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
}
//
try {
this.monthMatchers.add(ValueMatcherBuilder.build(parts[3 + offset], MONTH_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
}
// 星期几
try {
this.dayOfWeekMatchers.add(ValueMatcherBuilder.build(parts[4 + offset], DAY_OF_WEEK_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
}
//
if (parts.length == 7) {// 支持年的表达式
try {
this.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
}
} else {// 不支持年的表达式全部匹配
this.yearMatchers.add(new AlwaysTrueValueMatcher());
}
matcherSize++;
}
// -------------------------------------------------------------------------------------- Private method end
}

View File

@ -0,0 +1,119 @@
package cn.hutool.cron.pattern.matcher;
import java.time.Year;
import java.util.ArrayList;
import java.util.List;
/**
* 时间匹配表用于存放定时任务表达式解析后的结构信息
*
* @author looly
* @since 5.8.0
*/
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;
/**
* 构造
*
* @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);
}
/**
* 给定时间是否匹配定时任务表达式
*
* @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) {
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) {
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

@ -0,0 +1,132 @@
package cn.hutool.cron.pattern.parser;
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.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcherBuilder;
import java.util.List;
/**
* 定时任务表达式解析器用于将表达式字符串解析为{@link MatcherTable}
*
* @author looly
* @since 5.8.0
*/
public class CronPatternParser {
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();
/**
* 创建表达式解析器
*
* @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;
}
/**
* 解析复合任务表达式格式为
* <pre>
* cronA | cronB | ...
* </pre>
*
* @param groupPattern 复合表达式
*/
private void parseGroupPattern(String groupPattern) {
final List<String> patternList = StrUtil.split(groupPattern, '|');
matcherTable = new MatcherTable(patternList.size());
for (String pattern : patternList) {
parseSinglePattern(pattern);
}
}
/**
* 解析单一定时任务表达式
*
* @param pattern 表达式
*/
private void parseSinglePattern(String pattern) {
final String[] parts = pattern.split("\\s");
int offset = 0;// 偏移量用于兼容Quartz表达式当表达式有6或7项时第一项为秒
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);
}
//
if (1 == offset) {// 支持秒的表达式
try {
matcherTable.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
}
} else {// 不支持秒的表达式则第一位按照表达式生成时间的秒数赋值表示整分匹配
matcherTable.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER));
}
//
try {
matcherTable.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
}
// 小时
try {
matcherTable.hourMatchers.add(ValueMatcherBuilder.build(parts[1 + offset], HOUR_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
}
// 每月第几天
try {
matcherTable.dayOfMonthMatchers.add(ValueMatcherBuilder.build(parts[2 + offset], DAY_OF_MONTH_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
}
//
try {
matcherTable.monthMatchers.add(ValueMatcherBuilder.build(parts[3 + offset], MONTH_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
}
// 星期几
try {
matcherTable.dayOfWeekMatchers.add(ValueMatcherBuilder.build(parts[4 + offset], DAY_OF_WEEK_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
}
//
if (parts.length == 7) {// 支持年的表达式
try {
matcherTable.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER));
} catch (Exception e) {
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
}
} else {// 不支持年的表达式全部匹配
matcherTable.yearMatchers.add(new AlwaysTrueValueMatcher());
}
}
}

View File

@ -4,7 +4,7 @@ import cn.hutool.cron.CronException;
/**
* 月份值处理<br>
* 限定于1-121表示一月支持别名如一月是{@code jan}
* 限定于1-121表示一月支持别名忽略大小写如一月是{@code jan}
*
* @author Looly
*/