io.github.lab515.utils.Evaluator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qray Show documentation
Show all versions of qray Show documentation
remoting ondemand in java
package io.github.lab515.utils;
import java.util.*;
/**
* specifically for permission condition evaluation
* supported operator: \(escape) !, *, **, &, |, and special keyword not, true, false, and, or
* sample :
* /sf* && **Service.abc(*,,**.ABC*)
* function call can only return boolean evaluated results
* no optimization (jmp, skip) since it's only for conditional check
* no quote is allowed, any special chars need to use \ as escape
*
* permission namespace , same as java package,
* permission.permtype.permstrig
* feature.amdin tools and name
*/
public class Evaluator {
static class Token{
String val;
int pri;
Token _next;
Token _last;
Token operate(Token Op, Token Oper2) {
if(Oper2 == null){
return null;
}
if (Op._next != null) {
return this;
}
Op._next = null;
Op._last = this;
(_next != null ? _last : this)._next = Oper2;
_last = Oper2._next != null ? Oper2._last : Oper2;
_last._next = Op;
_last = Op;
return this;
}
Token(String val, int pri){
this.val = val;
this.pri = pri;
}
boolean is(String val, int pri){
return pri == this.pri && (val == null || val.equals(this.val));
}
boolean isStopper(){
return is(",", NA) || is(":",TRI) || is(")",NA);
}
}
static final int AND = 3, OR = 2, NOT = 4, CALL = 5, PARA = 6, OPER = 0, NA = -1, CONST = -2, MATCH = -3, TRI = 1;
static final int TOKEN = 0, COMMENT = 1, ESCAPE = 2;
static final String OP_CHARS = "#&|!()?:,";
static final int[] OP_PRIORITIES = {NA, AND, OR, NA, NA, NA, TRI, TRI, NA};
static final Token NULL_TOKEN = new Token("",NA);
static final HashMap keywords = new HashMap<>();
static {
keywords.put("true",CONST);
keywords.put("null",CONST);
keywords.put("false",CONST);
keywords.put("and",AND);
keywords.put("or",OR);
keywords.put("not",NA);
}
/**
* tokenizer, it will parse string in greedy mode, stop at operators or others
* ALWAYS case insensitive!
* @param expr
* @return
*/
private void parse(String expr, boolean caseSensitive) {
parseErr = null;
expression = null;
if(expr == null || expr.length() < 1) {
parseErr = "empty expression";
return;
}
LinkedList ret = new LinkedList<>();
StringBuilder tk = new StringBuilder();
StringBuilder tkSeg = new StringBuilder();
int mode = TOKEN;
int v = -1;
String str = null;
Integer pr = null;
int op = OPER;
for(char c : (caseSensitive ? expr : expr.toLowerCase()).toCharArray()){
if(mode == COMMENT){
if(c == '\n') {
mode = TOKEN;
}
}else if(mode == ESCAPE){
if (c == 'n') {
tk.append('\n');
tkSeg.append(' ');
}else if(c == 't'){
tk.append('\t');
tkSeg.append(' ');
}else if(Character.isLetter(c)){
tk.append(c);
tkSeg.append(' ');
}else{
tk.append(c); // doesn't care what it is
tkSeg.append(' ');
if(c == '*'){
op = MATCH;
}
}
mode = TOKEN;
}else{
if(c == '\\'){
mode = ESCAPE;
}else if((Character.isWhitespace(c) && (v = -1) == -1) || (v = OP_CHARS.indexOf(c)) >= 0) {
// look back, to see if any "and","or","not"
if(tk.length() > 0) {
pr = keywords.get(tkSeg.toString());
if(pr != null){
// split it
str = tk.substring(0,tk.length() - tkSeg.length()).trim();
if(str.length() > 0){
ret.add(new Token(str,op));
}
ret.add(new Token(pr == CONST && tkSeg.charAt(0) == 'n' ? "": tkSeg.toString(),pr));
tk.setLength(0);
op = OPER;
}
}
if(v >= 0){
if(tk.length() > 0){
ret.add(new Token(tk.toString().trim(),op));
tk.setLength(0);
op = OPER;
}
if (c == '#') {
mode = COMMENT;
}else{
ret.add(new Token(c+"", OP_PRIORITIES[v]));
}
}else if(tk.length() > 0){
tk.append(c);
}
tkSeg.setLength(0);
}else{
tk.append(c);
tkSeg.append(c);
if(c == '*'){
op = MATCH;
}
}
}
}
if(tk.length()>0){
pr = keywords.get(tkSeg.toString());
if(pr != null){
// split it
str = tk.substring(0,tk.length() - tkSeg.length()).trim();
if(str.length() > 0){
ret.add(new Token(str,op));
}
ret.add(new Token(pr == CONST && tkSeg.charAt(0) == 'f' ? "": tkSeg.toString(),pr));
tk.setLength(0);
}else {
ret.add(new Token(tk.toString().trim(), op));
}
}
expression = parseExpr(ret, NULL_TOKEN);
if(expression != null && !ret.isEmpty()){
parseErr = "unexpected more tokens";
expression = null;
}
}
private static boolean isVariableName(String t){
if(!(Character.isLetter(t.charAt(0)) || t.charAt(0) == '_')){
return false;
}
for(char c : t.toCharArray()){
if(!(Character.isLetter(c) || Character.isDigit(c) || c == '_' || c == '.')){
return false;
}
}
return true;
}
// function and not now is diferent
// !a => a ! ""
// a(b) => b call a
private Token parseExpr(LinkedList tks, Token fromOp){
// only support & | ! ? :
Token t = tks.peek();
if(t == null){
parseErr = "expect more tokens";
return null;
}
tks.pop();
Token next = null;
int paraC = 0;
if(t.pri != OPER && t.pri != CONST && t.pri != MATCH){
if(t.is("(", NA)){
if((next = parseExpr(tks, NULL_TOKEN)) == null){
return null;
}
if((t = tks.peek()) == null || !t.is(")",NA)) {
parseErr = "not enclosed bracket";
return null;
}
tks.pop();
t = next;
}else if(t.is("!",NA) || t.is("not",NA)){
t.pri = NOT;
tks.push(t);
t = new Token("",NA); // nop is not necessary
}else{
parseErr = "invalid token, expect operand but " + t.val;
return null;
}
}
while(!tks.isEmpty()){
// since no func call allowed
next = tks.peek();
if(next.isStopper()){ // we don't do match the enclose
return t;
}else if(next.pri <= 0) {
if (next.is("(",NA) && t._last == null && isVariableName(t.val)) {
tks.pop();
next = null;
paraC = 0;
while (true) {
if (tks.isEmpty()) {
parseErr = "expect token as parameter";
return null;
}
if (tks.peek().is(")",NA) && next == null) {
break;
}
paraC++;
if (next != null) {
if(next.operate(new Token(",", PARA), parseExpr(tks, NULL_TOKEN)) == null){
return null;
}
} else {
if((next = parseExpr(tks, NULL_TOKEN)) == null){
return null;
}
}
if (tks.isEmpty() || !tks.peek().is(",",NA)) {
break;
}
tks.pop();
}
if (tks.isEmpty() || !tks.peek().is(")",NA)) {
parseErr = "exepct ) at end of func call";
return null;
}
tks.pop();
if (next == null) {
next = new Token("", OPER);
}
t.pri = NA; // not a oper
t = next.operate(new Token(paraC+"",CALL), t);
//t.operate(new Token(paraC+"",CALL),next);
} else {
parseErr = "invalid function format, expect op but " + next.val;
return null;
}
}else {
if (fromOp != null && (fromOp.pri > next.pri || (fromOp.pri == next.pri && next.pri != NOT))) {
break;
}
tks.pop();
if (next.pri == TRI) { // no optimization jumps
if(next.is("?",TRI)) {
if(t.operate(next, parseExpr(tks, NULL_TOKEN))==null){
return null;
}
if (tks.isEmpty() || !((next = tks.pop()).is(":", TRI))) {
parseErr = "invalid ?: expr!";
return null;
}
}else{
parseErr = "invalid : expr";
return null;
}
}
//if(next.pri == NOT){
// if((t = parseExpr(tks, next).operate(next,t)) == null){
// return null;
// }
//}else{
if(t.operate(next, parseExpr(tks, next)) == null){
return null;
}
//}
}
}
return t;
}
private Token expression;
private String exprStr;
private String parseErr;
private String defVarVal;
private String defCallRet;
public Evaluator(String expr, String defVarVal, String defCallRet, boolean caseSensitive){
this.exprStr = expr;
this.defVarVal = defVarVal;
this.defCallRet = defCallRet;
parse(exprStr, caseSensitive);
}
public Evaluator(String expr, String defVarVal, String defCallRet){
this(expr,defVarVal,defCallRet,false);
}
public String getExpr(){
return exprStr;
}
public boolean run(Map vars, Object reserved, EvalOptions ec){
return evaluateBoolean(run2(vars,reserved, ec));
}
public String run2(Map vars, Object reserved, EvalOptions ec){
if(ec != null){
ec.beforeEvaluate(vars);;
}
String ret = evaluateExpr(vars,reserved, ec);
if(ec != null){
ret = ec.postEvaluate(ret);
}
return ret;
}
public String getParseError(){
return parseErr;
}
public static boolean evaluateBoolean(String v){
if(v == null || v.length() < 1){
return false;
}
if(v.equals("false") || v.equals("0") || v.equals("null")){
return false;
}
return true;
}
static class RunStack{
RunStack next;
String v;
String t;
RunStack(){
}
RunStack(String v, String t, RunStack next){
this.v =v;
this.t= t;
this.next = next;
}
void push(String v,String t){
next = new RunStack(v,t, next);
}
String pop(){
v = next.v;
t = next.t;
next = next.next;
return v;
}
String token(){
return t;
}
}
@SuppressWarnings("squid:S1643")
private String evaluateExpr(Map vars, Object reserved, EvalOptions ec){
RunStack runStack = new RunStack();
String oper1, oper2, temp;
String[] paras, paraNames;
Token expr = expression;
if(expr == null){
if(ec != null){
ec.raiseErr("invalid expression can not be executed", parseErr,true);
}
return null;
}
while(expr != null){
if(expr.pri <= OPER){
if(expr.pri != OPER && expr.pri != MATCH){
oper1 = expr.val; // only function call, or const
} else {
oper1 = null;
if(expr.pri == OPER){
oper1 = vars != null ? vars.get(expr.val) : null;
}
// for all
if (ec != null) {
oper1 = ec.resolveVariable(expr.val, oper1, expr.pri == MATCH);
}
if(oper1 == null && ec == null && defVarVal != null){
oper1 = defVarVal;
}else if(oper1 == null) {
return null;
}
}
runStack.push(oper1,expr.val);
}else{
oper2 = runStack.pop();
temp = runStack.token();
oper1 = runStack.pop();
switch (expr.pri){
case TRI: //? :
if(expr.val.equals("?")) {
if (evaluateBoolean(oper1)) {
oper2 = "?" + oper2; // reset
}else{
oper2 = "";
temp = "";
}
}else{ // :
if(oper1.startsWith("?")){
oper2 = oper1.substring(1);
temp = runStack.token();
}
}
break;
case OR:
if(evaluateBoolean(oper1)){
oper2 = oper1;
oper2 = runStack.token();
}
break;
case AND:
if(!evaluateBoolean(oper1)){
oper2 = "";
temp = "";
}
break;
case NOT:
oper2 = evaluateBoolean(oper2) ? "" : "1";
temp = "";
break;
case PARA:
runStack.push(oper1, runStack.token());
break;
case CALL:
paras = new String[Integer.parseInt(expr.val)];
paraNames = new String[paras.length];
if(paras.length > 0) {
paras[paras.length-1] = oper1;
paraNames[paras.length-1] = runStack.token();
for (int i = paras.length - 2; i >= 0; i--) {
paras[i] = runStack.pop();
paraNames[i] = runStack.token();
}
}
oper2 = ec != null ? ec.handleCall(oper2,paraNames, paras) : null;
temp = "";
if(oper2 == null && defCallRet == null){
return null;
}else if(oper2 == null){
oper2 = defCallRet;
}
break;
default:
if(ec != null){
ec.raiseErr("invalid op:" + expr.pri , expr.val,true);
}
return null;
}
runStack.push(oper2,temp);
}
expr = expr._next;
}
if(runStack.next == null || runStack.next.next != null){
if(ec != null){
ec.raiseErr("invalid runstack size:" + (runStack.next == null ? "0" : " more than 1"),"" ,true);
}
return null;
}
return runStack.pop();
}
protected Set getAllOpers(int pri){
HashSet ret = new HashSet<>();
Token t = expression;
while(t != null){
if(t.pri == pri){
ret.add(t.val);
}
t = t._next;
}
return ret;
}
}