語言類應用的好伙伴-antlr

之前做數據索引的時候接到一個需求:

我們在ES生產全文檢索的索引的時候, 會定義mapping, 并依據mapping編寫搜索邏輯, 并進行調優(yōu). 比如對于文章類的數據(任意業(yè)務方), 我們可以把它們的共性, 如標題, 副標題, 內容, 作者等固定為通用的mapping字段, 然后就可以很快的應用上基礎的搜索能力了. 但是, 業(yè)務方的數據, 是各種各樣的, 同樣是標題類的數據, 有的叫做title, 有的叫做head; 再比如, 有的業(yè)務方希望自己數據的標題其實是 以及一級標題 + 二級標題 + tags 這樣就給數據導入帶來了問題, 需要給每個業(yè)務方編寫數據轉化, 操作的邏輯. 占用了大量的人力, 并且發(fā)布更新都很麻煩.
所以: 希望能有一種方式來簡化這個流程, 讓數據的導入過程更加簡單, 減少人力和工作量.

讓數據的導入過程更加簡單 是終極業(yè)務需求, 我這里針對其中的一點: 給每個業(yè)務方編寫數據轉化, 操作的邏輯 闡述下我的解決思路.

舉例:
用戶提供的接口的數據:

{
  "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" 加和, 生成新的字段和數據.

這部分邏輯原先是用代碼完成的, 代碼是最靈活的, 可以滿足各種需求. 那為了能夠讓開發(fā)同學任然具有這份靈活性, 我決定還是使用"代碼" 的方式為他們提供支持, 類似規(guī)則引擎那樣, 開發(fā)同學編寫規(guī)則, 實際由規(guī)則引擎去執(zhí)行具體的操作.

拆解為: 規(guī)則的解析 + 規(guī)則的執(zhí)行

解析

規(guī)則的解析其實就是一個語言類應用嘛

語言類應用
● 文件讀取器: 配置文件讀取器, 方法調用分析工具之類的程序分析工具. java 的 class文件載入器, aroma
● 生成器: 收集內部數據結構信息, 產生輸出. 對象-關系數據庫映射工具, 序列化工具, 源代碼生成器, 網頁生成器
● 翻譯器: = 讀取器 + 生成器. 代碼插裝工具, 匯編器和編譯器.
● 解釋器: 讀取文件, 解碼, 執(zhí)行指令. 計算器, 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>

執(zhí)行

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是否規(guī)范的代碼.

解析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閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屯仗,死亡現場離奇詭異踩娘,居然都是意外死亡表谊,警方通過查閱死者的電腦和手機钞护,發(fā)現死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爆办,“玉大人难咕,你說我怎么就攤上這事。” “怎么了余佃?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵暮刃,是天一觀的道長。 經常有香客問我爆土,道長椭懊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任雾消,我火速辦了婚禮灾搏,結果婚禮上,老公的妹妹穿的比我還像新娘立润。我一直安慰自己狂窑,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布桑腮。 她就那樣靜靜地躺著泉哈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪破讨。 梳的紋絲不亂的頭發(fā)上丛晦,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音提陶,去河邊找鬼烫沙。 笑死,一個胖子當著我的面吹牛隙笆,可吹牛的內容都是我干的锌蓄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼撑柔,長吁一口氣:“原來是場噩夢啊……” “哼瘸爽!你這毒婦竟也來了?” 一聲冷哼從身側響起铅忿,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剪决,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后檀训,有當地人在樹林里發(fā)現了一具尸體柑潦,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年峻凫,在試婚紗的時候發(fā)現自己被綠了妒茬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔚晨,死狀恐怖乍钻,靈堂內的尸體忽然破棺而出肛循,到底是詐尸還是另有隱情,我是刑警寧澤银择,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布多糠,位于F島的核電站,受9級特大地震影響浩考,放射性物質發(fā)生泄漏夹孔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一析孽、第九天 我趴在偏房一處隱蔽的房頂上張望搭伤。 院中可真熱鬧,春花似錦袜瞬、人聲如沸怜俐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拍鲤。三九已至,卻和暖如春汞扎,著一層夾襖步出監(jiān)牢的瞬間季稳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工澈魄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留景鼠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓痹扇,卻偏偏與公主長得像莲蜘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帘营,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容