package org.n3r.eql.matrix; import com.alibaba.druid.util.StringUtils; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import org.n3r.eql.matrix.impl.MatrixFunction; import org.n3r.eql.matrix.impl.MatrixMapper; import org.n3r.eql.matrix.impl.MatrixRule; import org.n3r.eql.matrix.impl.MatrixTableField; import org.n3r.eql.util.C; import org.n3r.eql.util.S; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings("unchecked") public class RuleParser { public RulesSet parse(String ruleSpec) { Iterable<String> lines = Splitter.on('\n').trimResults().split(ruleSpec); RulesSet rulesSet = new RulesSet(); int lineNo = 0; for (String line : lines) { ++lineNo; if (StringUtils.isEmpty(line)) continue; if (line.startsWith("#")) continue; // comments try { if (line.startsWith("alias")) { parseAlias(rulesSet, line); } else if (line.startsWith("rule")) { parseRule(rulesSet, line); } else { throw new RuntimeException("unknown format"); } } catch (RuntimeException ex) { throw new RuntimeException(ex.getMessage() + " in line " + lineNo + " [" + line + "]"); } } return rulesSet; } static Pattern ruleIndexPattern = Pattern.compile("rule\\s*\\(\\s*(\\d+)\\s*\\)"); static Pattern javaIdentifierPattern = Pattern.compile("([_\\w][_\\w\\d]*)\\s*\\((.*?)\\)"); private void parseRule(RulesSet rulesSet, String line) { MatrixRule matrixRule = new MatrixRule(); String remain = S.trimToEmpty(line); remain = parseRuleNo(rulesSet, matrixRule, remain); remain = parseFunction(rulesSet, matrixRule, remain); parseMapper(rulesSet, matrixRule, remain); rulesSet.addRule(matrixRule); } private void parseMapper(RulesSet rulesSet, MatrixRule matrixRule, String remain) { if (StringUtils.isEmpty(remain)) throw new RuntimeException("rule is invalid without mapper"); Matcher matcher = javaIdentifierPattern.matcher(remain); boolean found = matcher.find(); if (!found || matcher.start() != 0) throw new RuntimeException("mappers invalid "); String mapperAlias = matcher.group(1); Class<? extends MatrixMapper> mapClass = rulesSet.getMapAlias(mapperAlias); if (mapClass == null) throw new RuntimeException("mapper is unknown "); MatrixMapper mapper = createMatrixMapper(mapClass); String mapperParamsStr = S.trimToEmpty(matcher.group(2)); if (S.isBlank(mapperParamsStr)) throw new RuntimeException("mapper is invalid "); List<String> mapperParams = Splitter.on(',').omitEmptyStrings().trimResults().splitToList(mapperParamsStr); mapper.config(mapperParams); matrixRule.mapper = mapper; } private String parseFunction(RulesSet rulesSet, MatrixRule matrixRule, String remain) { if (StringUtils.isEmpty(remain)) throw new RuntimeException("rule is invalid"); Matcher matcher = javaIdentifierPattern.matcher(remain); boolean found = matcher.find(); if (!found || matcher.start() != 0) throw new RuntimeException("function is invalid"); String funcAlias = matcher.group(1); Class<? extends MatrixFunction> funcClass = rulesSet.getFunctionAlias(funcAlias); if (funcClass == null) throw new RuntimeException("function is unknown"); String funcParams = S.trimToEmpty(matcher.group(2)); if (S.isBlank(funcParams)) throw new RuntimeException("function should have parameters"); MatrixFunction func = createMatrixFunction(funcClass); List<String> params = Splitter.on(',').omitEmptyStrings().trimResults().splitToList(funcParams); List<MatrixTableField> fields = Lists.newArrayList(); int i = parseFunctionRelativeTableFields(func, params, fields); List<String> realFuncParams = Lists.newArrayList(); for (int ii = params.size(); i < ii; ++i) { realFuncParams.add(params.get(i)); } func.configFunctionParameters(realFuncParams.toArray(new String[0])); matrixRule.function = func; return S.trimToEmpty(remain.substring(matcher.end())); } private int parseFunctionRelativeTableFields(MatrixFunction func, List<String> params, List<MatrixTableField> fields) { int i = 0; for (int ii = params.size(); i < ii; ++i) { String param = params.get(i); if (!param.startsWith(".")) break; int dotPos = param.indexOf('.', 1); if (dotPos <= 0 || dotPos == param.length() - 1) throw new RuntimeException("rule function should have at least one relative table field"); String tableName = param.substring(1, dotPos); String fieldName = param.substring(dotPos + 1); if (!aliasPattern.matcher(tableName).matches()) throw new RuntimeException("table name is invalid int rule function parameters"); if (!aliasPattern.matcher(fieldName).matches()) throw new RuntimeException("field name is invalid int rule function parameters"); fields.add(new MatrixTableField(tableName, fieldName)); } func.configRelativeTableFields(fields.toArray(new MatrixTableField[0])); return i; } private String parseRuleNo(RulesSet rulesSet, MatrixRule matrixRule, String remain) { Matcher matcher = ruleIndexPattern.matcher(remain); boolean found = matcher.find(); if (!found || matcher.start() != 0) throw new RuntimeException("rule is invalid"); int ruleNo = Integer.parseInt(matcher.group(1)); MatrixRule rule = rulesSet.getRule(ruleNo); if (rule != null) throw new RuntimeException("rule no is duplicated"); matrixRule.ruleNo = ruleNo; // parse function return S.trimToEmpty(remain.substring(matcher.end())); } private MatrixMapper createMatrixMapper(Class<? extends MatrixMapper> mapClass) { try { return mapClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } private MatrixFunction createMatrixFunction(Class<? extends MatrixFunction> funcClass) { try { return funcClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } static Pattern aliasPattern = Pattern.compile("[_\\w]([_\\w\\d])*"); private void parseAlias(RulesSet rulesSet, String line) { String map = line.substring("alias".length()); String alias = S.trimToEmpty(map); if (StringUtils.isEmpty(alias) || !alias.startsWith("(") || !alias.endsWith(")")) { throw new RuntimeException("alias required format: alias(shortName, FQCN)"); } alias = S.trimToEmpty(alias.substring(1, alias.length() - 1)); if (StringUtils.isEmpty(alias)) { throw new RuntimeException("alias required format: alias(shortName, FQCN)"); } int commaPos = alias.indexOf(','); if (commaPos <= 0 || commaPos == alias.length() - 1) { throw new RuntimeException("alias required format: alias(shortName, FQCN)"); } String aliasName = S.trimToEmpty(alias.substring(0, commaPos)); String fullName = S.trimToEmpty(alias.substring(commaPos + 1)); if (!aliasPattern.matcher(aliasName).matches()) { throw new RuntimeException("alias short name is invalid"); } Class<? extends MatrixFunction> fullClass = getFullClass(fullName); if (fullClass == null) { throw new RuntimeException("alias full name is invalid"); } rulesSet.addAlias(aliasName, fullClass); } private Class<? extends MatrixFunction> getFullClass(String fullName) { if (StringUtils.isEmpty(fullName)) return null; try { return (Class<? extends MatrixFunction>) Class.forName(fullName, false, C.getClassLoader()); } catch (ClassNotFoundException e) { return null; } } }