语言类应用的好伙伴-antlr

之前做数据索引的时候接到一个需求:

我们在ES生产全文检索的索引的时候, 会定义mapping, 并依据mapping编写搜索逻辑, 并进行调优. 比如对于文章类的数据(任意业务方), 我们可以把它们的共性, 如标题, 副标题, 内容, 作者等固定为通用的mapping字段, 然后就可以很快的应用上基础的搜索能力了. 但是, 业务方的数据, 是各种各样的, 同样是标题类的数据, 有的叫做title, 有的叫做head; 再比如, 有的业务方希望自己数据的标题其实是 以及一级标题 + 二级标题 + tags 这样就给数据导入带来了问题, 需要给每个业务方编写数据转化, 操作的逻辑. 占用了大量的人力, 并且发布更新都很麻烦.
所以: 希望能有一种方式来简化这个流程, 让数据的导入过程更加简单, 减少人力和工作量.

让数据的导入过程更加简单 是终极业务需求, 我这里针对其中的一点: 给每个业务方编写数据转化, 操作的逻辑 阐述下我的解决思路.

举例:
用户提供的接口的数据:

{
  "code" : 200,
  "msg" : "success",
  "result" : {
    "data" : [
        {
            "id": 2,
            "type" : "cat",
            "title_one": "beautiful animal",
            "title_two": "pets",
            "content" : "i have a   cat"
        }
      ]
  }
}

用户希望搜索标题的时候, 可以同时搜索"title_one", "title_two". 则我们需要一个设施可以操作用户的数据, 将"title_one", "title_two" 加和, 生成新的字段和数据.

这部分逻辑原先是用代码完成的, 代码是最灵活的, 可以满足各种需求. 那为了能够让开发同学任然具有这份灵活性, 我决定还是使用"代码" 的方式为他们提供支持, 类似规则引擎那样, 开发同学编写规则, 实际由规则引擎去执行具体的操作.

拆解为: 规则的解析 + 规则的执行

解析

规则的解析其实就是一个语言类应用嘛

语言类应用
● 文件读取器: 配置文件读取器, 方法调用分析工具之类的程序分析工具. java 的 class文件载入器, aroma
● 生成器: 收集内部数据结构信息, 产生输出. 对象-关系数据库映射工具, 序列化工具, 源代码生成器, 网页生成器
● 翻译器: = 读取器 + 生成器. 代码插装工具, 汇编器和编译器.
● 解释器: 读取文件, 解码, 执行指令. 计算器, python的实现.

如果要实现类似c语言风格的逻辑语言, 对我来说太难了, 还是借鉴了lisp, 采用简单的语法结构: S表达式, 关于lisp, 不去赘述, 我学的也不好....

但是希望可以用这样的方式来操作用户的json数据:

concat(max(list(2, 1, $user_data.clicknumber)), "something")
这段逻辑最终出来的数据是: 100someting, 一个json的str.

这是最简单的一个例子, 毕竟json还有object, list 等数据结构, 都要能够无损支持.

grammar CalcRefactorV1;

start : expr; // start rule, EOF if needed
// 没有类型检查
expr : list_expr
    | not_list_expr
    | compare_expr
    | condition
    | '(' expr ')'
    ;

list_expr: LIST '(' not_list_expr (',' not_list_expr)* ')' # ListCons
    | FLATTEN '(' list_expr (',' list_expr)* ')' # ListFlatten
//    | SUB '(' list_expr ')' # ListSub
    | LIST_VARIABLE # ListVar
    ;

not_list_expr : NOT_LIST_VARIABLE # NotListVar
    | CONCAT '(' not_list_expr (',' not_list_expr)* ')' # NotListConcat// 1 或多
//    | CONCAT '(' list_expr ')'
    | SIZE '(' list_expr ')' # ListSize
    | JOIN '(' not_list_expr ',' list_expr ')' # ListJoin
    | SUM '(' list_expr ')' # ListSum
    | SUM '(' not_list_expr (',' not_list_expr)* ')' # NotListSum
    | NUM # Num
    | STRING #  Str
    ;
    
//compare_expr :  COMPARE '(' expr ',' expr ')' # Compare ;
compare_expr :  COMPARE '(' not_list_expr ',' not_list_expr ')' # Compare ;

condition : CONDITION  '(' compare_expr ',' expr ',' expr ')' # Condi ;
//condition : CONDITION  '(' compare_expr ',' not_list_expr ',' not_list_expr ')' # Condi ;

// 简单字面量值
//value: NUM
//      | STRING
//;

CONCAT: 'concat';
FLATTEN: 'flatten';
//SUB: 'sub';
SUM: 'sum';
LIST: 'list';
SIZE: 'size';
JOIN: 'join';
CONDITION: 'condition';
COMPARE: 'compare';

// list variable: $.[*] $.[*].xxx  $.xx.yy12.[*] $.[*].xxx
// \$(\.[a-zA-Z_][a-zA-Z0-9_]*)*\.\[\*\]((\.[a-zA-Z_][a-zA-Z0-9_]*)|(\.\[\*\]))* ........                                        || 从这里开始的可以丢掉吧? 如果为了性能, 毕竟也不应该写这么复杂的
//LIST_VARIABLE: '$'('.'(ALPHA_)ALPHA__DIGIT*)*('.[*]'|('.'((ALPHA_)ALPHA__DIGIT*)?'['INT':'INT']'))(('.'(ALPHA_)ALPHA__DIGIT*)|'.[*]'|('.'((ALPHA_)ALPHA__DIGIT*)?'['INT':'INT']'))*  ;
LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)*('.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))(('.'ALPHA__DIGIT+)|'.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))*  ;
// $.lll.
NOT_LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)+;
NUM:  '-'?(DIGIT+ | DIGIT+'.'DIGIT+ | '.'DIGIT);
STRING: '"'(ESC|.)*?'"';
WS: [ \t\n\r]+ -> skip;
fragment
DIGIT: [0-9];
INT: '0'|'-'?[1-9]DIGIT*;
ALPHA: [a-zA-Z];
ALPHA_: ALPHA|'_';
ALPHA__DIGIT: ALPHA_|DIGIT;
ESC: '\\"' | '\\\\';

关于list和非list表达式,也可以协作

LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)*('.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))(('.'ALPHA__DIGIT+)|'.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))*  ;
// $.lll.
NOT_LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)+;

另外, 在pom里增加了一段处理罗辑, 每次编译前, 都要生成

    <build>
        <plugins>
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-maven-plugin</artifactId>
                <version>4.9.3</version>
                <configuration>
                    <!--<sourceDirectory>${basedir}/src/java</sourceDirectory>-->
                    <outputDirectory>${basedir}/src/main/java/</outputDirectory>
                    <visitor>true</visitor>
                    <listener>true</listener>
                </configuration>
                <executions>
                    <execution>
                        <id>antlr</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>antlr4</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

执行

package .................functions;

import ...........GsonJsonPathUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import .....................gen.*;

/**
 * This class provides an implementation and provides a way to calculate result of user defined expr: such as sum, size, etc.
 *  todo optimized
 */
public class CalculationVisitor extends CalcRefactorV1BaseVisitor<Function<String, JsonElement>> {

    /**
     * 暴露给上层的接口
     *
     * @param expr 用户定义的字符串表达式
     */
    public static Function<String, JsonElement> parseExpr(String expr) {
        CodePointCharStream           input             = CharStreams.fromString(expr);
        CalcRefactorV1Lexer           calcLexer         = new CalcRefactorV1Lexer(input);
        CommonTokenStream             commonTokenStream = new CommonTokenStream(calcLexer);
        CalcRefactorV1Parser          calcParser        = new CalcRefactorV1Parser(commonTokenStream);
        ParseTree                     tree              = calcParser.expr();
        CalculationVisitor            visitor           = new CalculationVisitor();
        Function<String, JsonElement> visit             = visitor.visit(tree);
        return visit;
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListCons(CalcRefactorV1Parser.ListConsContext ctx) {
        List<CalcRefactorV1Parser.Not_list_exprContext> exprs     = ctx.not_list_expr();
        List<Function<String, JsonElement>>             argsFuncs = exprs.stream().map(this::visit).collect(Collectors.toList());
        return new ListCons(argsFuncs);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListFlatten(CalcRefactorV1Parser.ListFlattenContext ctx) {
        List<CalcRefactorV1Parser.List_exprContext> listExprContexts = ctx.list_expr();
        List<Function<String, JsonElement>> argsFuncs = listExprContexts.stream().map(this::visit).collect(
                Collectors.toList());
        return new Flatten(argsFuncs);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListVar(CalcRefactorV1Parser.ListVarContext ctx) {
        String variable = ctx.getText();
        return s -> GsonJsonPathUtils.read(s, variable);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitNotListVar(CalcRefactorV1Parser.NotListVarContext ctx) {
        String variable = ctx.getText();
        return s -> GsonJsonPathUtils.read(s, variable);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitNotListConcat(CalcRefactorV1Parser.NotListConcatContext ctx) {
        List<CalcRefactorV1Parser.Not_list_exprContext> notListExprContexts = ctx.not_list_expr();
        List<Function<String, JsonElement>> argsFuncs = notListExprContexts.stream().map(this::visit).collect(
                Collectors.toList());

        return new NotListConcat(argsFuncs);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListSize(CalcRefactorV1Parser.ListSizeContext ctx) {
        CalcRefactorV1Parser.List_exprContext listExprContext = ctx.list_expr();
        Function<String, JsonElement>         argsFunc        = visit(listExprContext);
        return new Size(argsFunc);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListJoin(CalcRefactorV1Parser.ListJoinContext ctx) {
        CalcRefactorV1Parser.Not_list_exprContext notListExprContext = ctx.not_list_expr();
        CalcRefactorV1Parser.List_exprContext     list_exprContext   = ctx.list_expr();

        Function<String, JsonElement> seperatorFunc = visit(notListExprContext);
        Function<String, JsonElement> arrayFunc     = visit(list_exprContext);

        return new Join(seperatorFunc, arrayFunc);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitListSum(CalcRefactorV1Parser.ListSumContext ctx) {
        Function<String, JsonElement> functions = visit(ctx.list_expr());

        return new ListSum(functions);
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitNotListSum(CalcRefactorV1Parser.NotListSumContext ctx) {
        List<CalcRefactorV1Parser.Not_list_exprContext> notListExprContexts = ctx.not_list_expr();
        List<Function<String, JsonElement>>             ans                 = new LinkedList<>();
        for (CalcRefactorV1Parser.Not_list_exprContext notListExprContext : notListExprContexts) {
            Function<String, JsonElement> visit = visit(notListExprContext);
            ans.add(visit);
        }

        return new NotListSum(ans);

    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitNum(CalcRefactorV1Parser.NumContext ctx) {
        return s -> new JsonPrimitive(Integer.parseInt(ctx.NUM().getText()));
    }

    /**
     * @param ctx the parse tree
     * @return func
     */
    @Override
    public Function<String, JsonElement> visitStr(CalcRefactorV1Parser.StrContext ctx) {
        return s -> new JsonPrimitive(ctx.getText().substring(1, ctx.getText().length() - 1));
    }

}

附录

在antlr4的项目里面, 有很多的g4文件, 都是网友贡献的

这是json的语法文件, 接下来尝试写了个解析和判断json是否规范的代码.

解析json结构


/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */

// Derived from http://json.org
grammar JSON;

json
   : value EOF
   ;

obj
   : '{' pair (',' pair)* '}'
//   | '{' '}'
   ;

pair
   : STRING ':' value
   ;

arr
   : '[' value (',' value)* ']'
//   | '[' ']'
   ;

value
   : STRING
   | NUMBER
   | obj
   | arr
   | 'true'
   | 'false'
   | 'null'
   ;


STRING
   : '"' (ESC | SAFECODEPOINT)* '"'
   ;


fragment ESC
   : '\\' (["\\/bfnrt] | UNICODE)
   ;


fragment UNICODE
   : 'u' HEX HEX HEX HEX
   ;


fragment HEX
   : [0-9a-fA-F]
   ;


fragment SAFECODEPOINT
   : ~ ["\\\u0000-\u001F]
   ;


NUMBER
   : '-'? INT ('.' [0-9] +)? EXP?
   ;


fragment INT
   : '0' | [1-9] [0-9]*
   ;

// no leading zeros

fragment EXP
   : [Ee] [+\-]? INT
   ;

// \- since - means "range" inside [...]

WS
   : [ \t\n\r] + -> skip
   ;
import .......gen.*;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class JsonStructureVisitor extends JSONBaseVisitor<DataStructure> {

    public static DataStructure parseJson(String json) {
        CodePointCharStream  input             = CharStreams.fromString(json);
        JSONLexer            lexer             = new JSONLexer(input);
        CommonTokenStream    commonTokenStream = new CommonTokenStream(lexer);
        JSONParser           parser            = new JSONParser(commonTokenStream);
        ParseTree            tree              = parser.json();
        JsonStructureVisitor visitor           = new JsonStructureVisitor();
        DataStructure        visit             = visitor.visit(tree);
        return visit;
    }

    @Override
    public DataStructure visitJson(JSONParser.JsonContext ctx) {
        if (Objects.equals(ctx.value().getText(), "null")) {
            return null;
        }

        return visit(ctx.value());
    }

    @Override
    public DataStructure visitObj(JSONParser.ObjContext ctx) {
        ObjectStructure obj = new ObjectStructure();
        for (JSONParser.PairContext pairContext : ctx.pair()) {
            DataStructure child = visit(pairContext.value());
            if (child != null) {
                obj.addField(pairContext.STRING().getText());
                obj.addFieldType(child);
            }
        }
        return obj;
    }

    @Override
    public DataStructure visitPair(JSONParser.PairContext ctx) {
        return super.visitPair(ctx);
    }

    @Override
    public DataStructure visitArr(JSONParser.ArrContext ctx) {
        ListStructure listStructure = new ListStructure();
        Map<String, List<DataStructure>> collect = ctx.value().stream().map(this::visit).collect(
                Collectors.groupingBy(x-> GsonInstance.getGson().toJson(x)));
        if (collect.size() > 1) {
            throw new RuntimeException(String.format("list: %s 中只能有一种类型", ctx.getText()));
        }
        listStructure.setItemType(collect.values().iterator().next().get(0));
        return listStructure;
    }

    @Override
    public DataStructure visitValue(JSONParser.ValueContext ctx) {
        if (ctx.STRING() != null) {
            return new PrimitiveStructure("string");
        }
        if (ctx.NUMBER() != null) {
            return new PrimitiveStructure("number");
        }
        if (Objects.equals(ctx.getText(), "true") || Objects.equals(ctx.getText(), "false")) {
            return new PrimitiveStructure("boolean");
        }

        return super.visitValue(ctx);
    }
}

工具类

需要对json数据进行解析, 探查, 操作, 利用的是Gson + jsonpath

操作和解析

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;

import java.util.Map;
import java.util.Objects;

public class GsonJsonPathUtils {
    private static final Configuration config;

    static {
        Configuration.ConfigurationBuilder builder = Configuration.builder();
        builder.jsonProvider(new GsonJsonProvider());
        builder.mappingProvider(new GsonMappingProvider());
        config = builder.build();
    }

    /**
     * todo: unfinished method, 给定某个数据结构, 验证path表达式, 是否合法
     *
     * @param ds          the data structure
     * @param jsonPathMap the json path map
     * @param path        the json path
     */
    public static void constructAllJsonPath(DataStructure ds, Map<String, JsonPath> jsonPathMap, String path) {
        if (Objects.equals(ds.type(), DataStructure.object)) {
            ObjectStructure obj = (ObjectStructure) ds;
            for (int i = 0; i < obj.getFields().size(); i++) {
                String fieldName = obj.getField(i);
                String newP      = path + "." + fieldName;
                jsonPathMap.put(newP, JsonPath.compile(path));
                constructAllJsonPath(obj.getFieldsType(i), jsonPathMap, newP);
            }
        }
    }

    /**
     * @param jsObj  gson 的 json object
     * @param jsPath compiled json path
     * @param <T>    type of result, jsObj, jsArray, jsPrimitive
     * @return 从某个json中获取的值
     */
    public static <T> T read(Object jsObj, JsonPath jsPath) {
        return JsonPath.using(config).parse(jsObj).read(jsPath);
    }

    /**
     * @param jsObj     gson 的 json object
     * @param jsPathStr have not compiled json path
     * @param <T>       type of result, jsObj, jsArray, jsPrimitive
     * @return 从某个json中获取的值
     */
    public static <T> T read(Object jsObj, String jsPathStr) {
        return JsonPath.using(config).parse(jsObj).read(jsPathStr);
    }

    /**
     * @param str    gson 的 原始字符串
     * @param jsPath compiled json path
     * @param <T>    type of result, jsObj, jsArray, jsPrimitive
     * @return 从某个json中获取的值
     */
    public static <T> T read(String str, JsonPath jsPath) {
        return JsonPath.using(config).parse(str).read(jsPath);
    }

    /**
     * @param str       gson 的 json object
     * @param jsPathStr haven't compiled json path
     * @param <T>       type of result, jsObj, jsArray, jsPrimitive
     * @return 从某个json中获取的值
     */
    public static <T> T read(String str, String jsPathStr) {
        return JsonPath.using(config).parse(str).read(jsPathStr);
    }

    /**
     * @param str       gson 的 原始字符串
     * @param jsPathStr compiled json path
     * @return 获取json中的这个路径代表的元素的长度, 一定得是数组, 否则抛异常
     */
    public static int length(String str, String jsPathStr) {
        return JsonPath.using(config).parse(str).read(jsPathStr + ".length()");
    }

    /**
     * @param jsObj     gson 的 json object
     * @param jsPathStr haven't compiled json path
     * @return 获取json中的这个路径代表的元素的长度, 一定得是数组, 否则抛异常
     */
    public static int length(Object jsObj, String jsPathStr) {
        return JsonPath.using(config).parse(jsObj).read(jsPathStr + ".length()");
    }

}

注册自定义数据类型和解析器

    public static final GsonBuilder builder;
    public static final Gson        gson;

    static {
        builder = new GsonBuilder();
        builder.registerTypeAdapter(DataStructure.class, new GsonDataStructureSerde());
        gson = builder.create();
    }

自定义序列化, 反序列化

数据类型

这个类, 可以用来表示json的结构:object, list, primitive.

import com.alibaba.fastjson.annotation.JSONType;

//@JSONType(seeAlso = {ListStructure.class, ObjectStructure.class, PrimitiveStructure.class})
// NOTICE:  如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
public abstract class DataStructure {

    public static final String list      = "list";
    public static final String primitive = "primitive";
    public static final String object    = "object";

    private final String type;

    public DataStructure(String type) {
        this.type = type;
    }

    public String type() {
        return this.type;
    }

    public boolean isPrimitive() {
        return this.type.equals(primitive);
    }

    public boolean isList() {
        return this.type.equals(list);
    }

    public boolean isObject() {
        return this.type.equals(object);
    }

    public abstract boolean selfValidate();

}
import com.alibaba.fastjson.annotation.JSONType;

import java.util.Objects;

//@JSONType(typeName = "list")
public class ListStructure extends DataStructure {
    public DataStructure itemType;

    public ListStructure() {
        super(list);
    }

    public void setItemType(DataStructure itemType) {
        this.itemType = itemType;
    }


    // NOTICE:  如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
    @Override
    public String toString() {
        return "ListStructure{" +
                "itemType=" + itemType +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof ListStructure) {
            ListStructure other = (ListStructure) obj;
            return Objects.equals(this.itemType, other.itemType);
        }
        return false;

    }

    @Override
    public boolean selfValidate() {
        return itemType != null && itemType.selfValidate();
    }
}
import com.alibaba.fastjson.annotation.JSONType;
import lombok.Getter;

import java.util.*;
import java.util.function.Consumer;

@Getter
//@JSONType(typeName = "object")
public class ObjectStructure extends DataStructure implements Iterable<AbstractMap.SimpleImmutableEntry<String, DataStructure>> {

    public List<String>        fields     = new ArrayList<>();
    public List<DataStructure> fieldsType = new ArrayList<>();

    public ObjectStructure() {
        super(object);
    }

    public void addField(String key) {
        this.fields.add(key);
    }

    public void addFieldType(DataStructure type) {
        this.fieldsType.add(type);
    }

    // NOTICE:  如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
    @Override
    public String toString() {
        return "ObjectStructure{" +
                "fields=" + fields +
                ", fieldsType=" + fieldsType +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof ObjectStructure) {
            ObjectStructure other = (ObjectStructure) obj;
            boolean         b1    = this.fields.size() == other.fields.size();
            boolean         b2    = this.fieldsType.size() == other.fieldsType.size();
            if (!(b1 && b2)) {
                return false;
            }
            for (int i = 0; i < this.fields.size(); i++) {
                String                  field          = this.fields.get(i);
                Optional<DataStructure> thisFieldType  = this.getFieldType(field);
                Optional<DataStructure> otherFieldType = other.getFieldType(field);
                Boolean                 isEqual        = thisFieldType.flatMap(t -> otherFieldType.map(t::equals)).orElse(false);
                if (!isEqual) {
                    return false;
                }

            }
            return true;
        }
        return false;
    }

    public Optional<DataStructure> getFieldType(String fieldName) {
        int i = fields.indexOf(fieldName);
        return (i >= 0) ? Optional.of(fieldsType.get(i)) : Optional.empty();
    }

    public String getField(int i) {
        return this.fields.get(i);
    }

    public DataStructure getFieldsType(int i) {
        return this.fieldsType.get(i);
    }

    @Override
    public Iterator<AbstractMap.SimpleImmutableEntry<String, DataStructure>> iterator() {
        return new Iterator<AbstractMap.SimpleImmutableEntry<String, DataStructure>>() {
            private int i = 0;

            @Override
            public boolean hasNext() {
                return i < fields.size();
            }

            @Override
            public AbstractMap.SimpleImmutableEntry<String, DataStructure> next() {
                i += 1;
                return new AbstractMap.SimpleImmutableEntry<>(fields.get(i - 1), fieldsType.get(i - 1));
            }
        };
    }

    @Override
    public boolean selfValidate() {
        return fields.size() == fieldsType.size() && fieldsType.stream().allMatch(DataStructure::selfValidate);
    }
}

import com.alibaba.fastjson.annotation.JSONType;
import io.swagger.v3.oas.models.media.Schema;

import java.util.*;

//@JSONType(typeName = "primitive")
public class PrimitiveStructure extends DataStructure {
    private String pType  = "string"; // number, string, boolean // todo date
    private String format = ""; // byte,short, integer,long, unsigned_long, double, float, // todo unknown, 浮点 + 整型

    public static final List<String> pTypes       = Arrays.asList("string", "number", "boolean");
    public static final List<String> swaggerTypes = Collections.unmodifiableList(Arrays.asList("string", "number", "boolean", "integer"));

    public static PrimitiveStructure swaggerSchema2DataStructureConverter(Schema<?> schema) {
        assert PrimitiveStructure.swaggerTypes.contains(schema.getType()); // 将所有非object, array的结构都认作原子/基本结构
        String type;
        String format = "";
        if (Objects.equals(schema.getType(), "string")) {
            type = "string";
        } else if (Objects.equals(schema.getType(), "number")) {
            type = "number";
            format = schema.getFormat() == null ? "" : schema.getFormat();
        } else if (Objects.equals(schema.getType(), "integer")) {
            type = "number";
            format = schema.getFormat() != null ? Objects.equals(schema.getFormat(), "int32") ? "integer" : "long" : "integer";
        } else if (Objects.equals(schema.getType(), "boolean")) {
            type = "boolean";
        } else {
            throw new RuntimeException("unknown swagger type: " + schema.getType());
        }

        PrimitiveStructure primitiveStructure = new PrimitiveStructure(type);
        primitiveStructure.setFormat(format);
        return primitiveStructure;

    }

    // 在swagger文档中是这样定义数据类型的
    // https://swagger.io/docs/specification/data-models/data-types/
    //number    –   Any numbers.
    //number    float   Floating-point numbers.
    //number    double  Floating-point numbers with double precision.
    //integer   –   Integer numbers.
    //integer   int32   Signed 32-bit integers (commonly used integer type).
    //integer   int64   Signed 64-bit integers (long type).
    public PrimitiveStructure(String pType) {
        super(primitive);
        if (!pTypes.contains(pType)) {
            throw new RuntimeException("unknown pType: " + pType);
        }
        this.pType = pType;
    }

    // NOTICE:  如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
    @Override
    public String toString() {
        return "PrimitiveStructure{" + "pType='" + pType + '\'' + ", format='" + format + '\'' + '}';
    }

    @Override
    public boolean selfValidate() {
        boolean p = Objects.equals(pType, "string") || Objects.equals(pType, "number") || Objects.equals(pType, "boolean");
        boolean f = format != null;
        return p && f;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public String getPType() {
        return this.pType;
    }

    public String getFormat() {
        return this.format;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof PrimitiveStructure) {
            PrimitiveStructure other = (PrimitiveStructure) obj;
            return Objects.equals(this.pType, other.pType);
        }
        return false;
    }
}```
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容