java设计模式之解释器

解释器模式听起来很复杂,但应用范围很小众,只会在一些特定领域才会用到,比如编译器、规则引擎,正则表达式。

正好我在项目中用到了这个功能。作为例子总结一下。

我的项目中实现了一个API注册的功能,简单来说就是用户通过注册一个数据源和一个SQL,我给他返回一个API接口。

本身逻辑并不复杂,但因为很多指标是跟日期相关的。需要给SQL语句提供一个宏变量占位符的功能,这样才能保证随着时间的变化,用户可能想要看本日、昨日、本月、上月这些日期维度的数据,我们在不改变SQL的情况下,都能满足这些需求。

显然这些日期不能再SQL中写死。我们需要提供一种规则,用户按规则填写一个字符串,比如"${yyyy-MM-dd:-1d}",我再SQL执行之前,将它替换成昨天的日期。

在下面的实现中,我使用了两次正则表达式,第一次是将"${}"中的字符串识别为宏变量,第二次才是对宏变量的解释。

一、宏变量的提取

public class DateFormatUtil {
    private static final Pattern dynamicPattern = Pattern.compile("\\$\\{(.*?)\\}");
    private static final Pattern namePattern = Pattern.compile("(?<format>[yMd\\-]+):?(?<op>\\+|\\-)?(?<num>[0-9]+)?(?<scale>[a-zA-Z])?");

    /**
     * 正则查找动态日期字符串'${yyyy-MM-dd}或'${yyyyMMdd:+7d}',并进行格式化
     * @param sourStr
     * @return
     */
    public static String dynamicFormat(String sourStr) {
        String targetStr = sourStr;

        try {
            Matcher dynamicMatcher = dynamicPattern.matcher(targetStr);
            while (dynamicMatcher.find()) {
                String key = dynamicMatcher.group();
                String keyclone = key.substring(2,key.length()-1);
                String value = getDynamicDate(keyclone);
                if (value != null)
                    targetStr = targetStr.replace(key, value);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return targetStr;
    }

    /**
     * 根据日期格式返回日期字符串
     * @param key 'yyyyMMdd' 或 'yyyy-MM-dd:+1d'
     * @return 2021-11-23
     * 根据格式串分别匹配出日期格式、加or减、加减数量和尺度
     */
    public static String getDynamicDate(String key) {
        Matcher dateMatcher = namePattern.matcher(key);
        LocalDateTime now = LocalDateTime.now();
        String target=null;
        while (dateMatcher.find()) {
            String format= dateMatcher.group("format");
            String op= dateMatcher.group("op");
            String num= dateMatcher.group("num");
            String scale= dateMatcher.group("scale");
            DateFormatEnum dynamicDate = getDateFormat(format);
            if (op == null) {
                return dynamicDate.format(now);
            }

            DateScaleEnum dateScaleEnum = DateScaleEnum.valueOf(scale);
            LocalDateTime calculate = dateScaleEnum.calculate(now, op, Integer.valueOf(num));

            target= dynamicDate.format(calculate);

        }
        return target;
    }

    /**
     * 根据用户提供的format对日期格式化
     * @param format
     * @return
     */
    public static DateFormatEnum getDateFormat(String format) {
        switch (format) {
            case "yyyy":
                return DateFormatEnum.Y;
            case "yyyy-MM":
                return DateFormatEnum.YM;
            case "yyyyMM":
                return DateFormatEnum.YM3;
            case "yyyyMMdd":
                return DateFormatEnum.YMD3;
            case "yyyy-MM-dd":
            default:
                return  DateFormatEnum.YMD;
        }
    }

    public static void main(String[] args) {
        String sql="select a,b,c from table " +
                "where dt>='${yyyy:-1y}-01' and dt<='${yyyyMM:+10M}' and dt='${yyyy-MM-dd} 00:00:00'";
        System.out.println(sql);
        String newSql = dynamicFormat(sql);
        System.out.println(newSql);
    }
}

二、宏变量的解释

对宏变量的解析分为两种情况,一种是没有日期加减的情况,只需要根据用户提交的占位符,转换成对应格式的日期

public enum DateFormatEnum {
    YMD_HMS(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) {
        public TemporalAccessor parse(String src) {
            return this.getFormatter().parse(src);
        }
    },
    YMD(DateTimeFormatter.ofPattern("yyyy-MM-dd")) {
        public TemporalAccessor parse(String src) {
            return this.getFormatter().parse(src);
        }
    },
    YMD2(DateTimeFormatter.ofPattern("yyyy/MM/dd")) {
        public TemporalAccessor parse(String src) {
            return this.getFormatter().parse(src);
        }
    },
    YMD3(DateTimeFormatter.ofPattern("yyyyMMdd")) {
        public TemporalAccessor parse(String src) {
            return this.getFormatter().parse(src);
        }
    },
    YM(DateTimeFormatter.ofPattern("yyyy-MM")) {
        public TemporalAccessor parse(String src) {
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("yyyy-MM")
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    YM2(DateTimeFormatter.ofPattern("yyyy年M月")) {
        public TemporalAccessor parse(String src) {
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("yyyy年M月")
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    YM3(DateTimeFormatter.ofPattern("yyyyMM")) {
        public TemporalAccessor parse(String src) {
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("yyyyMM")
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    Y(DateTimeFormatter.ofPattern("yyyy")) {
        public TemporalAccessor parse(String src) {
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("yyyy")
                    .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    MD(DateTimeFormatter.ofPattern("MM/dd")) {
        public TemporalAccessor parse(String src) {
            int year = LocalDateTime.now().getYear();
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("MM/dd")
                    .parseDefaulting(ChronoField.YEAR, year)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    MD2(DateTimeFormatter.ofPattern("MM月dd日")) {
        public TemporalAccessor parse(String src) {
            int year = LocalDateTime.now().getYear();
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("MM月dd日")
                    .parseDefaulting(ChronoField.YEAR, year)
                    .toFormatter();
            return formatter.parse(src);
        }
    },
    MD_HM(DateTimeFormatter.ofPattern("MM-dd HH:mm")) {
        public TemporalAccessor parse(String src) {
            int year = LocalDateTime.now().getYear();
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("MM-dd HH:mm")
                    .parseDefaulting(ChronoField.YEAR, year)
                    .toFormatter();
            return formatter.parse(src);
        }
    };

    private final DateTimeFormatter formatter;

    DateFormatEnum(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    public String format(TemporalAccessor temporalAccessor) {
        return formatter.format(temporalAccessor);
    }

    public DateTimeFormatter getFormatter() {
        return formatter;
    }

    public abstract TemporalAccessor parse(String src);

    public static void main(String[] args) {
        String src = "2021/04/01";
        TemporalAccessor parse = YMD2.parse(src);
        String format = YMD.format(parse);
        System.out.println(format);
    }
}

 

三、宏变量的计算

在前面的基础上,我们还需要日期的加减,我们只支持加减两种操作。所有没有定义单独的类,而是作为一个参数传进来。

不过加减的度量可以是年月日三种,都在下面的类中定义好了。

public enum DateScaleEnum {
    y {
        @Override
        public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) {
            if (op.equals("+")) {
                return origin.plusYears(num);
            } else {
                return origin.minusYears(num);
            }
        }
    },
    M {
        @Override
        public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) {
            if (op.equals("+")) {
                return origin.plusMonths(num);
            } else {
                return origin.minusMonths(num);
            }
        }
    },
    d {
        @Override
        public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) {
            if (op.equals("+")) {
                return origin.plusDays(num);
            } else {
                return origin.minusDays(num);
            }
        }
    };

    public abstract LocalDateTime calculate(LocalDateTime origin, String op, Integer num);
}

当我们遇到这样一个SQL时:

sql="select a,b,c from table " +
                "where dt>='${yyyy:-1y}-01' and dt<='${yyyyMM:+10M}' and dt='${yyyy-MM-dd} 00:00:00'";

就可以通过我们的解释规则将'${yyyyMM:+10M}'理解为用户需要10个月之后的年月格式数据,进而替换成用户需要的日期。

posted @ 2022-01-24 16:34  Mars.wang  阅读(158)  评论(0编辑  收藏  举报