This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-jcasbin.git
The following commit(s) were added to refs/heads/master by this push:
new e8e973d feat: replace Aviator with a built-in matcher expression
evaluator
e8e973d is described below
commit e8e973dee7417ab80477638545beeb591050875d
Author: Yang Luo <[email protected]>
AuthorDate: Thu Nov 5 10:50:22 2026 +0800
feat: replace Aviator with a built-in matcher expression evaluator
---
README.md | 56 ++--
pom.xml | 6 -
.../EvaluationContext.java} | 35 +--
.../org/casbin/jcasbin/expression/Expression.java | 87 ++++++
.../jcasbin/expression/ExpressionException.java | 31 ++
.../jcasbin/expression/ExpressionFunction.java | 45 +++
.../java/org/casbin/jcasbin/expression/Lexer.java | 205 +++++++++++++
.../org/casbin/jcasbin/expression/Operations.java | 215 +++++++++++++
.../java/org/casbin/jcasbin/expression/Parser.java | 335 +++++++++++++++++++++
.../casbin/jcasbin/expression/PropertyReader.java | 124 ++++++++
.../java/org/casbin/jcasbin/main/CoreEnforcer.java | 92 ++----
.../casbin/jcasbin/main/ManagementEnforcer.java | 9 +-
.../java/org/casbin/jcasbin/model/FunctionMap.java | 34 +--
.../org/casbin/jcasbin/util/BuiltInFunctions.java | 187 +++++-------
.../casbin/jcasbin/util/ExpressionValidator.java | 67 -----
src/main/java/org/casbin/jcasbin/util/Util.java | 40 ---
.../jcasbin/util/function/CustomFunction.java | 38 +--
.../org/casbin/jcasbin/util/function/EvalFunc.java | 26 +-
.../casbin/jcasbin/util/function/FunctionArgs.java | 60 ++++
.../jcasbin/util/function/GlobMatchFunc.java | 19 +-
.../casbin/jcasbin/util/function/IPMatchFunc.java | 19 +-
.../casbin/jcasbin/util/function/KeyGet2Func.java | 21 +-
.../casbin/jcasbin/util/function/KeyGet3Func.java | 21 +-
.../casbin/jcasbin/util/function/KeyGetFunc.java | 19 +-
.../jcasbin/util/function/KeyMatch2Func.java | 19 +-
.../jcasbin/util/function/KeyMatch3Func.java | 19 +-
.../jcasbin/util/function/KeyMatch4Func.java | 19 +-
.../jcasbin/util/function/KeyMatch5Func.java | 19 +-
.../casbin/jcasbin/util/function/KeyMatchFunc.java | 19 +-
.../jcasbin/util/function/RegexMatchFunc.java | 20 +-
.../jcasbin/main/BuiltInFunctionsUnitTest.java | 13 +-
.../org/casbin/jcasbin/main/ExpressionTest.java | 228 ++++++++++++++
.../jcasbin/main/ExpressionValidatorTest.java | 234 --------------
.../java/org/casbin/jcasbin/main/FunctionTest.java | 12 +-
.../casbin/jcasbin/main/ManagementAPIUnitTest.java | 23 --
.../org/casbin/jcasbin/main/ModelUnitTest.java | 12 +-
.../jcasbin/main/SyncedEnforcerUnitTest.java | 6 +-
.../casbin/jcasbin/main/TestAviatorValidation.java | 64 ----
.../java/org/casbin/jcasbin/main/TestUtil.java | 6 +-
.../java/org/casbin/jcasbin/main/UtilTest.java | 26 --
40 files changed, 1617 insertions(+), 913 deletions(-)
diff --git a/README.md b/README.md
index 33bf0f4..563f128 100644
--- a/README.md
+++ b/README.md
@@ -215,43 +215,45 @@ https://casbin.apache.org/docs/adapters
https://casbin.apache.org/docs/role-managers
-## Expression Validation and Cross-Platform Compatibility
+## Matcher expressions
-Starting from version 1.98.1, jCasbin validates expressions to ensure
cross-platform compatibility with other Casbin implementations (Go, Node.js,
Python, .NET, etc.).
+jCasbin evaluates matchers and `eval()` rules with its own expression
evaluator (`org.casbin.jcasbin.expression`). It has the same syntax and
operator precedence as the Go implementation, so a model gives the same
decisions in every Casbin implementation.
-### Restricted Syntax
+- **Literals**: `'text'` or `"text"`, numbers, `true`, `false`, `nil`
+- **Variables**: `r.sub`, `p.obj`, and attributes such as `r.sub.Age`.
Attributes are read from map entries, getters (`getAge()`, `isAdmin()`), record
accessors and public fields; no other methods are called.
+- **Arrays**: `('admin', 'editor')` or `['admin', 'editor']`, usually with `in`
+- **Operators**, from the lowest precedence: `? :` and `??`, `||`, `&&`, `==
!= < > <= >= =~ !~ in`, `|`, `^`, `&`, `<< >>`, `+ -`, `* / %`, `**`, unary `-
! ~`
+- **Built-in functions**: `g()`, `keyMatch()`, `keyMatch2()` to `keyMatch5()`,
`keyGet()`, `keyGet2()`, `keyGet3()`, `regexMatch()`, `ipMatch()`,
`globMatch()`, `eval()`
-The following AviatorScript-specific features are **not allowed** in `eval()`
expressions and policy rules:
-
-- **Namespace methods**: `seq.list()`, `string.startsWith()`,
`string.endsWith()`, `math.sqrt()`, etc.
-- **Advanced control structures**: `lambda`, `let`, `fn`, `for`, `while`,
`return`, `if-then-else`, `->`
-
-These features are restricted because they are specific to AviatorScript and
would make policies incompatible with other Casbin implementations.
-
-### Allowed Syntax
+```java
+"g(r.sub, p.sub) && keyMatch(r.path, p.path)"
+"r.role in ('admin', 'editor')"
+"eval(p.sub_rule)" // with a policy rule such as: r.sub.Age > 18 && r.sub.Age
< 65
+```
-The following standard Casbin syntax is fully supported:
+Anything else, such as AviatorScript's `seq.list()`, `string.startsWith()` or
`lambda`, is a syntax error. A matcher with a syntax error throws an
`ExpressionException`; an `eval()` rule with one is logged as a warning and
evaluates to `false`.
-- **Operators**: `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `+`, `-`, `*`,
`/`, `!`, `in`
-- **Built-in functions**: `g()`, `keyMatch()`, `keyMatch2-5()`,
`regexMatch()`, `ipMatch()`, `globMatch()`, `timeMatch()`, `eval()`
-- **Custom functions**: Users can still register custom functions using
`enforcer.addFunction()`
-- **Variable access**: `r.attr`, `p.attr` (automatically escaped to `r_attr`,
`p_attr`)
+### Custom functions
-### Example
+A custom function receives the evaluated arguments (`String`, `Long`,
`Double`, `Boolean`, `List`, or the objects passed to `enforce()`) and returns
the result:
```java
-// ❌ NOT allowed - AviatorScript-specific syntax
-"eval(seq.list('admin', 'editor'))"
-"eval(string.startsWith(r.path, '/admin'))"
-
-// ✅ Allowed - Standard Casbin syntax
-"eval(r.age > 18 && r.age < 65)"
-"r.role in ('admin', 'editor')" // Converted to include(tuple(...), ...)
-"g(r.sub, p.sub) && keyMatch(r.path, p.path)"
+enforcer.addFunction("isOwner", args -> args[0].equals(args[1]));
+
+enforcer.addFunction("isPublic", new CustomFunction() {
+ @Override
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("isPublic", args, 1);
+ return FunctionArgs.getString(args, 0).startsWith("/public/");
+ }
+
+ @Override
+ public String getName() {
+ return "isPublic";
+ }
+});
```
-If an expression contains restricted syntax, it will be logged as a warning
and return `false`.
-
## Examples
| Model | Model file
| Policy file
|
diff --git a/pom.xml b/pom.xml
index 05727ea..280d8ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -261,12 +261,6 @@ under the License.
<version>4.11.0</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>io.github.aviatorscript</groupId>
- <artifactId>aviator</artifactId>
- <version>5.9.0</version>
- </dependency>
-
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
diff --git a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
b/src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
similarity index 51%
copy from src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
copy to src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
index 2889fcb..2176106 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
+++ b/src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
@@ -15,32 +15,29 @@
// specific language governing permissions and limitations
// under the License.
-package org.casbin.jcasbin.util.function;
-
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import org.casbin.jcasbin.util.BuiltInFunctions;
+package org.casbin.jcasbin.expression;
+import java.util.Collections;
import java.util.Map;
/**
- * EvalFunc is the wrapper for eval.
- * It extends CustomFunction, so it can be used in matcher and policy rule.
- *
- * @author shink
+ * EvaluationContext holds the variables (such as r_sub, p_obj) and the
functions
+ * an expression is evaluated with.
*/
-public class EvalFunc extends CustomFunction {
+public class EvaluationContext {
+ private final Map<String, Object> variables;
+ private final Map<String, ? extends ExpressionFunction> functions;
+
+ public EvaluationContext(Map<String, Object> variables, Map<String, ?
extends ExpressionFunction> functions) {
+ this.variables = variables == null ? Collections.emptyMap() :
variables;
+ this.functions = functions == null ? Collections.emptyMap() :
functions;
+ }
- @Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
- String eval = FunctionUtils.getStringValue(arg1, env);
- eval = replaceTargets(eval, env);
- return AviatorBoolean.valueOf(BuiltInFunctions.eval(eval, env,
getAviatorEval()));
+ public Map<String, Object> getVariables() {
+ return variables;
}
- @Override
- public String getName() {
- return "eval";
+ public Map<String, ? extends ExpressionFunction> getFunctions() {
+ return functions;
}
}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Expression.java
b/src/main/java/org/casbin/jcasbin/expression/Expression.java
new file mode 100644
index 0000000..da50f42
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Expression.java
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.util.Map;
+
+/**
+ * Expression is a compiled matcher expression, such as
+ * {@code g(r_sub, p_sub) && keyMatch(r_obj, p_obj) && r_act == p_act}.
+ * It is immutable and can be evaluated concurrently with different variables.
+ *
+ * <p>The language is the one shared by all Casbin implementations: string,
number,
+ * boolean and null literals, arrays like {@code ('a', 'b')} or {@code ['a',
'b']},
+ * variables with attribute access like {@code r_sub.Age}, function calls, and
the
+ * operators {@code ?: ?? || && == != < > <= >= =~ !~ in | ^ & << >> + - * / %
! ~ **}.
+ * Attribute access reads map entries, getters, record accessors and public
fields only.
+ */
+public final class Expression {
+ private final String source;
+ private final Parser.Node root;
+
+ private Expression(String source, Parser.Node root) {
+ this.source = source;
+ this.root = root;
+ }
+
+ /**
+ * compile parses an expression.
+ *
+ * @param source the expression text.
+ * @return the compiled expression.
+ * @throws ExpressionException if the expression is not valid.
+ */
+ public static Expression compile(String source) {
+ if (source == null) {
+ throw new ExpressionException("expression is null");
+ }
+ return new Expression(source, Parser.parse(source));
+ }
+
+ /**
+ * evaluate evaluates the expression.
+ *
+ * @param variables the variables, such as r_sub and p_obj.
+ * @param functions the functions the expression can call.
+ * @return the result.
+ * @throws ExpressionException if the evaluation fails.
+ */
+ public Object evaluate(Map<String, Object> variables, Map<String, ?
extends ExpressionFunction> functions) {
+ return evaluate(new EvaluationContext(variables, functions));
+ }
+
+ /**
+ * evaluate evaluates the expression.
+ *
+ * @param context the variables and functions.
+ * @return the result.
+ * @throws ExpressionException if the evaluation fails.
+ */
+ public Object evaluate(EvaluationContext context) {
+ return root.eval(context);
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ @Override
+ public String toString() {
+ return source;
+ }
+}
diff --git
a/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java
b/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java
new file mode 100644
index 0000000..1b5e24d
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+/**
+ * ExpressionException is thrown when a matcher expression cannot be parsed or
evaluated.
+ */
+public class ExpressionException extends RuntimeException {
+ public ExpressionException(String message) {
+ super(message);
+ }
+
+ public ExpressionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git
a/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java
b/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java
new file mode 100644
index 0000000..caf7c92
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+/**
+ * ExpressionFunction is a function that can be called from a matcher, such as
keyMatch(r.obj, p.obj).
+ */
+@FunctionalInterface
+public interface ExpressionFunction {
+ /**
+ * call invokes the function.
+ *
+ * @param args the evaluated arguments: String, Long, Double, Boolean,
null,
+ * List for array literals, or any object passed in the
request.
+ * @return the result, usually a Boolean or a String.
+ */
+ Object call(Object... args);
+
+ /**
+ * call invokes the function with access to the variables and functions of
the
+ * current evaluation. Override it when the function needs them, like
eval() does.
+ *
+ * @param context the current evaluation context.
+ * @param args the evaluated arguments.
+ * @return the result.
+ */
+ default Object call(EvaluationContext context, Object... args) {
+ return call(args);
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Lexer.java
b/src/main/java/org/casbin/jcasbin/expression/Lexer.java
new file mode 100644
index 0000000..4e5fcd2
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Lexer.java
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Lexer {
+ enum Kind {
+ NUMBER, STRING, IDENTIFIER, OPERATOR, EOF
+ }
+
+ static class Token {
+ final Kind kind;
+ final String text;
+ final Object value;
+ final int position;
+
+ Token(Kind kind, String text, Object value, int position) {
+ this.kind = kind;
+ this.text = text;
+ this.value = value;
+ this.position = position;
+ }
+
+ boolean is(String operator) {
+ return kind == Kind.OPERATOR && text.equals(operator);
+ }
+
+ @Override
+ public String toString() {
+ return kind == Kind.EOF ? "end of expression" : "'" + text + "'";
+ }
+ }
+
+ private static final String[] OPERATORS = {
+ "**", "==", "!=", ">=", "<=", "=~", "!~", "&&", "||", "<<", ">>", "??",
+ "+", "-", "*", "/", "%", ">", "<", "!", "~", "&", "|", "^", "?", ":",
"(", ")", "[", "]", ","
+ };
+
+ private final String source;
+ private int pos;
+
+ private Lexer(String source) {
+ this.source = source;
+ }
+
+ static List<Token> tokenize(String source) {
+ Lexer lexer = new Lexer(source);
+ List<Token> tokens = new ArrayList<>();
+ Token token;
+ do {
+ token = lexer.next();
+ tokens.add(token);
+ } while (token.kind != Kind.EOF);
+ return tokens;
+ }
+
+ private Token next() {
+ while (pos < source.length() &&
Character.isWhitespace(source.charAt(pos))) {
+ pos++;
+ }
+ if (pos >= source.length()) {
+ return new Token(Kind.EOF, "", null, pos);
+ }
+
+ char c = source.charAt(pos);
+ if (Character.isDigit(c)) {
+ return number();
+ }
+ if (c == '\'' || c == '"') {
+ return string(c);
+ }
+ if (Character.isJavaIdentifierStart(c)) {
+ return identifier();
+ }
+ for (String op : OPERATORS) {
+ if (source.startsWith(op, pos)) {
+ Token token = new Token(Kind.OPERATOR, op, null, pos);
+ pos += op.length();
+ return token;
+ }
+ }
+ throw new ExpressionException("unexpected character '" + c + "' at
position " + pos + " in: " + source);
+ }
+
+ private Token number() {
+ int start = pos;
+ boolean decimal = false;
+ while (pos < source.length() && Character.isDigit(source.charAt(pos)))
{
+ pos++;
+ }
+ if (pos + 1 < source.length() && source.charAt(pos) == '.' &&
Character.isDigit(source.charAt(pos + 1))) {
+ decimal = true;
+ pos++;
+ while (pos < source.length() &&
Character.isDigit(source.charAt(pos))) {
+ pos++;
+ }
+ }
+ if (pos < source.length() && (source.charAt(pos) == 'e' ||
source.charAt(pos) == 'E')) {
+ int mark = pos;
+ pos++;
+ if (pos < source.length() && (source.charAt(pos) == '+' ||
source.charAt(pos) == '-')) {
+ pos++;
+ }
+ if (pos < source.length() &&
Character.isDigit(source.charAt(pos))) {
+ decimal = true;
+ while (pos < source.length() &&
Character.isDigit(source.charAt(pos))) {
+ pos++;
+ }
+ } else {
+ pos = mark;
+ }
+ }
+ String text = source.substring(start, pos);
+ Object value;
+ if (decimal) {
+ value = Double.parseDouble(text);
+ } else {
+ try {
+ value = Long.parseLong(text);
+ } catch (NumberFormatException e) {
+ value = Double.parseDouble(text);
+ }
+ }
+ return new Token(Kind.NUMBER, text, value, start);
+ }
+
+ private Token string(char quote) {
+ int start = pos;
+ pos++;
+ StringBuilder sb = new StringBuilder();
+ while (pos < source.length()) {
+ char c = source.charAt(pos);
+ if (c == quote) {
+ pos++;
+ return new Token(Kind.STRING, source.substring(start, pos),
sb.toString(), start);
+ }
+ if (c == '\\' && pos + 1 < source.length()) {
+ char e = source.charAt(pos + 1);
+ pos += 2;
+ switch (e) {
+ case 'n': sb.append('\n'); break;
+ case 't': sb.append('\t'); break;
+ case 'r': sb.append('\r'); break;
+ case 'b': sb.append('\b'); break;
+ case 'f': sb.append('\f'); break;
+ case '\\': sb.append('\\'); break;
+ case '\'': sb.append('\''); break;
+ case '"': sb.append('"'); break;
+ case 'u':
+ if (pos + 4 <= source.length()) {
+ try {
+ sb.append((char)
Integer.parseInt(source.substring(pos, pos + 4), 16));
+ pos += 4;
+ break;
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ sb.append("\\u");
+ break;
+ default:
+ // Keep unknown escapes such as \d as written, so
regex patterns work.
+ sb.append('\\').append(e);
+ }
+ continue;
+ }
+ sb.append(c);
+ pos++;
+ }
+ throw new ExpressionException("unterminated string starting at
position " + start + " in: " + source);
+ }
+
+ private Token identifier() {
+ int start = pos;
+ pos++;
+ while (pos < source.length()) {
+ char c = source.charAt(pos);
+ if (Character.isJavaIdentifierPart(c)) {
+ pos++;
+ } else if (c == '.' && pos + 1 < source.length() &&
Character.isJavaIdentifierStart(source.charAt(pos + 1))) {
+ pos += 2;
+ } else {
+ break;
+ }
+ }
+ String text = source.substring(start, pos);
+ return new Token(Kind.IDENTIFIER, text, null, start);
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Operations.java
b/src/main/java/org/casbin/jcasbin/expression/Operations.java
new file mode 100644
index 0000000..269b308
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Operations.java
@@ -0,0 +1,215 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+final class Operations {
+ private static final int MAX_CACHED_PATTERNS = 1000;
+ private static final Map<String, Pattern> PATTERNS = new
ConcurrentHashMap<>();
+
+ private Operations() {
+ }
+
+ static boolean toBoolean(Object value, String operator) {
+ if (value instanceof Boolean) {
+ return (Boolean) value;
+ }
+ throw new ExpressionException("value " + describe(value) + " cannot be
used with '" + operator + "', it is not a boolean");
+ }
+
+ static boolean equal(Object left, Object right) {
+ if (left == null || right == null) {
+ return left == right;
+ }
+ if (left instanceof Number && right instanceof Number) {
+ return compareNumbers((Number) left, (Number) right) == 0;
+ }
+ if (left instanceof Character) {
+ left = left.toString();
+ }
+ if (right instanceof Character) {
+ right = right.toString();
+ }
+ return Objects.equals(left, right);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ static int compare(Object left, Object right, String operator) {
+ if (left instanceof Number && right instanceof Number) {
+ return compareNumbers((Number) left, (Number) right);
+ }
+ if (isText(left) && isText(right)) {
+ return left.toString().compareTo(right.toString());
+ }
+ if (left instanceof Comparable && right != null && left.getClass() ==
right.getClass()) {
+ return ((Comparable) left).compareTo(right);
+ }
+ throw new ExpressionException("values " + describe(left) + " and " +
describe(right) + " cannot be compared with '" + operator + "'");
+ }
+
+ static boolean in(Object needle, Object haystack) {
+ if (haystack instanceof Collection) {
+ for (Object item : (Collection<?>) haystack) {
+ if (equal(needle, item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (haystack != null && haystack.getClass().isArray()) {
+ int length = Array.getLength(haystack);
+ for (int i = 0; i < length; i++) {
+ if (equal(needle, Array.get(haystack, i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ throw new ExpressionException("the right side of 'in' must be an array
or a collection, got " + describe(haystack));
+ }
+
+ static boolean regexMatch(Object text, Object pattern) {
+ if (!isText(text) || !isText(pattern)) {
+ throw new ExpressionException("'=~' needs a string and a pattern,
got " + describe(text) + " and " + describe(pattern));
+ }
+ String regex = pattern.toString();
+ Pattern compiled = PATTERNS.get(regex);
+ if (compiled == null) {
+ try {
+ compiled = Pattern.compile(regex);
+ } catch (PatternSyntaxException e) {
+ throw new ExpressionException("invalid regular expression '" +
regex + "'", e);
+ }
+ if (PATTERNS.size() >= MAX_CACHED_PATTERNS) {
+ PATTERNS.clear();
+ }
+ PATTERNS.put(regex, compiled);
+ }
+ return compiled.matcher(text.toString()).find();
+ }
+
+ static Object arithmetic(String operator, Object left, Object right) {
+ if ("+".equals(operator) && (isText(left) || isText(right))) {
+ return String.valueOf(left) + right;
+ }
+ if (!(left instanceof Number) || !(right instanceof Number)) {
+ throw new ExpressionException("values " + describe(left) + " and "
+ describe(right) + " cannot be used with '" + operator + "', they are not
numbers");
+ }
+ Number a = (Number) left;
+ Number b = (Number) right;
+ // Division is always floating point, as in Go.
+ if (isIntegral(a) && isIntegral(b) && !"/".equals(operator)) {
+ long x = a.longValue();
+ long y = b.longValue();
+ switch (operator) {
+ case "+": return x + y;
+ case "-": return x - y;
+ case "*": return x * y;
+ case "%":
+ return y == 0 ? (Object) Double.NaN : (Object) (x % y);
+ case "**":
+ if (y >= 0) {
+ double power = Math.pow(x, y);
+ if (Math.abs(power) < 0x1p63) {
+ return (long) power;
+ }
+ return power;
+ }
+ return Math.pow(x, y);
+ case "&": return x & y;
+ case "|": return x | y;
+ case "^": return x ^ y;
+ case "<<": return x << y;
+ case ">>": return x >> y;
+ default:
+ break;
+ }
+ } else {
+ double x = a.doubleValue();
+ double y = b.doubleValue();
+ switch (operator) {
+ case "+": return x + y;
+ case "-": return x - y;
+ case "*": return x * y;
+ case "/": return x / y;
+ case "%": return x % y;
+ case "**": return Math.pow(x, y);
+ case "&": return a.longValue() & b.longValue();
+ case "|": return a.longValue() | b.longValue();
+ case "^": return a.longValue() ^ b.longValue();
+ case "<<": return a.longValue() << b.longValue();
+ case ">>": return a.longValue() >> b.longValue();
+ default:
+ break;
+ }
+ }
+ throw new ExpressionException("unknown operator '" + operator + "'");
+ }
+
+ static Object negate(Object value) {
+ if (value instanceof Number) {
+ Number n = (Number) value;
+ return isIntegral(n) ? (Object) (-n.longValue()) : (Object)
(-n.doubleValue());
+ }
+ throw new ExpressionException("value " + describe(value) + " cannot be
negated, it is not a number");
+ }
+
+ static Object bitwiseNot(Object value) {
+ if (value instanceof Number) {
+ return ~((Number) value).longValue();
+ }
+ throw new ExpressionException("value " + describe(value) + " cannot be
used with '~', it is not a number");
+ }
+
+ private static int compareNumbers(Number a, Number b) {
+ if (isIntegral(a) && isIntegral(b)) {
+ return Long.compare(a.longValue(), b.longValue());
+ }
+ if (a instanceof BigDecimal || b instanceof BigDecimal || a instanceof
BigInteger || b instanceof BigInteger) {
+ return new BigDecimal(a.toString()).compareTo(new
BigDecimal(b.toString()));
+ }
+ return Double.compare(a.doubleValue(), b.doubleValue());
+ }
+
+ private static boolean isIntegral(Number n) {
+ return n instanceof Long || n instanceof Integer || n instanceof Short
|| n instanceof Byte;
+ }
+
+ private static boolean isText(Object value) {
+ return value instanceof String || value instanceof Character;
+ }
+
+ static String describe(Object value) {
+ if (value == null) {
+ return "null";
+ }
+ if (value instanceof String) {
+ return "'" + value + "' (String)";
+ }
+ return value + " (" + value.getClass().getSimpleName() + ")";
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Parser.java
b/src/main/java/org/casbin/jcasbin/expression/Parser.java
new file mode 100644
index 0000000..632da0c
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Parser.java
@@ -0,0 +1,335 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import org.casbin.jcasbin.expression.Lexer.Kind;
+import org.casbin.jcasbin.expression.Lexer.Token;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Parser builds the syntax tree of a matcher. Operator precedence follows the
Go
+ * implementation (govaluate), from lowest to highest:
+ * ?: and ??, ||, &&, comparisons (== != < > <= >= =~ !~
in), |, ^, &,
+ * << >>, + -, * / %, **, unary - ! ~. Binary operators are
left-associative.
+ */
+class Parser {
+ interface Node {
+ Object eval(EvaluationContext context);
+ }
+
+ private static final Set<String> COMPARATORS = new
HashSet<>(Arrays.asList("==", "!=", "<", ">", "<=", ">=", "=~", "!~"));
+
+ private final String source;
+ private final List<Token> tokens;
+ private int index;
+
+ private Parser(String source) {
+ this.source = source;
+ this.tokens = Lexer.tokenize(source);
+ }
+
+ static Node parse(String source) {
+ Parser parser = new Parser(source);
+ Node node = parser.ternary();
+ if (parser.peek().kind != Kind.EOF) {
+ throw parser.error("unexpected " + parser.peek());
+ }
+ return node;
+ }
+
+ private Token peek() {
+ return tokens.get(index);
+ }
+
+ private Token advance() {
+ return tokens.get(index++);
+ }
+
+ private boolean accept(String operator) {
+ if (peek().is(operator)) {
+ index++;
+ return true;
+ }
+ return false;
+ }
+
+ private void expect(String operator) {
+ if (!accept(operator)) {
+ throw error("expected '" + operator + "' but found " + peek());
+ }
+ }
+
+ private ExpressionException error(String message) {
+ return new ExpressionException(message + " at position " +
peek().position + " in: " + source);
+ }
+
+ private Node ternary() {
+ Node condition = or();
+ if (accept("?")) {
+ Node whenTrue = ternary();
+ expect(":");
+ Node whenFalse = ternary();
+ return ctx -> Operations.toBoolean(condition.eval(ctx), "?") ?
whenTrue.eval(ctx) : whenFalse.eval(ctx);
+ }
+ if (accept("??")) {
+ Node fallback = ternary();
+ return ctx -> {
+ Object value = condition.eval(ctx);
+ return value != null ? value : fallback.eval(ctx);
+ };
+ }
+ return condition;
+ }
+
+ private Node or() {
+ Node left = and();
+ while (accept("||")) {
+ Node l = left;
+ Node r = and();
+ left = ctx -> Operations.toBoolean(l.eval(ctx), "||") ||
Operations.toBoolean(r.eval(ctx), "||");
+ }
+ return left;
+ }
+
+ private Node and() {
+ Node left = comparison();
+ while (accept("&&")) {
+ Node l = left;
+ Node r = comparison();
+ left = ctx -> Operations.toBoolean(l.eval(ctx), "&&") &&
Operations.toBoolean(r.eval(ctx), "&&");
+ }
+ return left;
+ }
+
+ private Node comparison() {
+ Node left = binary(0);
+ while (true) {
+ Token token = peek();
+ String op;
+ if (token.kind == Kind.OPERATOR &&
COMPARATORS.contains(token.text)) {
+ op = token.text;
+ } else if (token.kind == Kind.IDENTIFIER &&
token.text.equals("in")) {
+ op = "in";
+ } else {
+ return left;
+ }
+ index++;
+ Node l = left;
+ Node r = op.equals("in") && peek().is("(") ? inList() : binary(0);
+ left = comparator(op, l, r);
+ }
+ }
+
+ // After 'in', a parenthesized single value such as ('admin') is a
one-element list, as in Go.
+ private Node inList() {
+ expect("(");
+ List<Node> items = list(")");
+ if (items.size() != 1 || tokens.get(index - 2).is(",")) {
+ return array(items);
+ }
+ Node item = items.get(0);
+ return ctx -> {
+ Object value = item.eval(ctx);
+ if (value instanceof Collection || (value != null &&
value.getClass().isArray())) {
+ return value;
+ }
+ return Collections.singletonList(value);
+ };
+ }
+
+ private static Node comparator(String op, Node l, Node r) {
+ switch (op) {
+ case "==": return ctx -> Operations.equal(l.eval(ctx),
r.eval(ctx));
+ case "!=": return ctx -> !Operations.equal(l.eval(ctx),
r.eval(ctx));
+ case "<": return ctx -> Operations.compare(l.eval(ctx),
r.eval(ctx), op) < 0;
+ case ">": return ctx -> Operations.compare(l.eval(ctx),
r.eval(ctx), op) > 0;
+ case "<=": return ctx -> Operations.compare(l.eval(ctx),
r.eval(ctx), op) <= 0;
+ case ">=": return ctx -> Operations.compare(l.eval(ctx),
r.eval(ctx), op) >= 0;
+ case "=~": return ctx -> Operations.regexMatch(l.eval(ctx),
r.eval(ctx));
+ case "!~": return ctx -> !Operations.regexMatch(l.eval(ctx),
r.eval(ctx));
+ default: return ctx -> Operations.in(l.eval(ctx), r.eval(ctx));
+ }
+ }
+
+ // Left-associative binary levels between comparisons and unary operators,
lowest first.
+ private static final String[][] BINARY_LEVELS = {
+ {"|"}, {"^"}, {"&"}, {"<<", ">>"}, {"+", "-"}, {"*", "/", "%"}
+ };
+
+ private Node binary(int level) {
+ if (level == BINARY_LEVELS.length) {
+ return power();
+ }
+ Node left = binary(level + 1);
+ while (true) {
+ String op = null;
+ for (String candidate : BINARY_LEVELS[level]) {
+ if (peek().is(candidate)) {
+ op = candidate;
+ break;
+ }
+ }
+ if (op == null) {
+ return left;
+ }
+ index++;
+ Node l = left;
+ Node r = binary(level + 1);
+ String operator = op;
+ left = ctx -> Operations.arithmetic(operator, l.eval(ctx),
r.eval(ctx));
+ }
+ }
+
+ private Node power() {
+ Node left = unary();
+ while (accept("**")) {
+ Node l = left;
+ Node r = unary();
+ left = ctx -> Operations.arithmetic("**", l.eval(ctx),
r.eval(ctx));
+ }
+ return left;
+ }
+
+ private Node unary() {
+ if (accept("!")) {
+ Node operand = unary();
+ return ctx -> !Operations.toBoolean(operand.eval(ctx), "!");
+ }
+ if (accept("-")) {
+ Node operand = unary();
+ return ctx -> Operations.negate(operand.eval(ctx));
+ }
+ if (accept("~")) {
+ Node operand = unary();
+ return ctx -> Operations.bitwiseNot(operand.eval(ctx));
+ }
+ return primary();
+ }
+
+ private Node primary() {
+ Token token = advance();
+ switch (token.kind) {
+ case NUMBER:
+ case STRING:
+ Object value = token.value;
+ return ctx -> value;
+ case IDENTIFIER:
+ return identifier(token);
+ case OPERATOR:
+ if (token.text.equals("(")) {
+ List<Node> items = list(")");
+ if (items.size() == 1 && !tokens.get(index - 2).is(",")) {
+ return items.get(0);
+ }
+ return array(items);
+ }
+ if (token.text.equals("[")) {
+ return array(list("]"));
+ }
+ break;
+ default:
+ break;
+ }
+ index--;
+ throw error("unexpected " + token);
+ }
+
+ private Node identifier(Token token) {
+ String name = token.text;
+ switch (name) {
+ case "true":
+ return ctx -> Boolean.TRUE;
+ case "false":
+ return ctx -> Boolean.FALSE;
+ case "nil":
+ case "null":
+ return ctx -> null;
+ case "in":
+ index--;
+ throw error("unexpected 'in'");
+ default:
+ break;
+ }
+
+ if (accept("(")) {
+ if (name.indexOf('.') >= 0) {
+ index--;
+ throw error("unknown function '" + name + "'");
+ }
+ List<Node> args = list(")");
+ Node[] argNodes = args.toArray(new Node[0]);
+ return ctx -> {
+ ExpressionFunction function = ctx.getFunctions().get(name);
+ if (function == null) {
+ throw new ExpressionException("unknown function '" + name
+ "' in: " + source);
+ }
+ Object[] values = new Object[argNodes.length];
+ for (int i = 0; i < argNodes.length; i++) {
+ values[i] = argNodes[i].eval(ctx);
+ }
+ return function.call(ctx, values);
+ };
+ }
+
+ String[] path = name.split("\\.");
+ if (path.length == 1) {
+ return ctx -> ctx.getVariables().get(name);
+ }
+ return ctx -> {
+ Object value = ctx.getVariables().get(path[0]);
+ for (int i = 1; i < path.length; i++) {
+ value = PropertyReader.get(value, path[i]);
+ }
+ return value;
+ };
+ }
+
+ // Parses comma-separated items up to the closing bracket, which is
consumed.
+ private List<Node> list(String close) {
+ List<Node> items = new ArrayList<>();
+ if (accept(close)) {
+ return items;
+ }
+ do {
+ if (peek().is(close)) {
+ break;
+ }
+ items.add(ternary());
+ } while (accept(","));
+ expect(close);
+ return items;
+ }
+
+ private static Node array(List<Node> items) {
+ Node[] nodes = items.toArray(new Node[0]);
+ return ctx -> {
+ List<Object> values = new ArrayList<>(nodes.length);
+ for (Node node : nodes) {
+ values.add(node.eval(ctx));
+ }
+ return Collections.unmodifiableList(values);
+ };
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java
b/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java
new file mode 100644
index 0000000..d20b744
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java
@@ -0,0 +1,124 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * PropertyReader reads attributes such as r.sub.Age from request objects: map
entries,
+ * JavaBean getters, record accessors and public fields. It never calls other
methods.
+ */
+final class PropertyReader {
+ private interface Accessor {
+ Object get(Object target) throws ReflectiveOperationException;
+ }
+
+ private static final Accessor MISSING = target -> null;
+ private static final Map<Class<?>, Map<String, Accessor>> ACCESSORS = new
ConcurrentHashMap<>();
+
+ private PropertyReader() {
+ }
+
+ static Object get(Object target, String name) {
+ if (target == null) {
+ return null;
+ }
+ if (target instanceof Map) {
+ return ((Map<?, ?>) target).get(name);
+ }
+
+ Class<?> type = target.getClass();
+ Accessor accessor = ACCESSORS.computeIfAbsent(type, k -> new
ConcurrentHashMap<>())
+ .computeIfAbsent(name, k -> find(type, name));
+ if (accessor == MISSING) {
+ throw new ExpressionException("no property '" + name + "' on " +
type.getName());
+ }
+ try {
+ return accessor.get(target);
+ } catch (InvocationTargetException e) {
+ throw new ExpressionException("failed to read property '" + name +
"' on " + type.getName(), e.getCause());
+ } catch (ReflectiveOperationException e) {
+ throw new ExpressionException("failed to read property '" + name +
"' on " + type.getName(), e);
+ }
+ }
+
+ private static Accessor find(Class<?> type, String name) {
+ if (name.isEmpty()) {
+ return MISSING;
+ }
+ String capitalized = Character.toUpperCase(name.charAt(0)) +
name.substring(1);
+ Method method = getter(type, "get" + capitalized);
+ if (method == null) {
+ method = getter(type, "is" + capitalized);
+ if (method != null && method.getReturnType() != boolean.class &&
method.getReturnType() != Boolean.class) {
+ method = null;
+ }
+ }
+ if (method == null && isRecord(type)) {
+ method = getter(type, name);
+ }
+ if (method != null) {
+ if (method.getName().equals("getClass")) {
+ return MISSING;
+ }
+ Method m = method;
+ return m::invoke;
+ }
+
+ try {
+ Field field = type.getField(name);
+ if (!Modifier.isStatic(field.getModifiers())) {
+ makeAccessible(field);
+ return field::get;
+ }
+ } catch (NoSuchFieldException ignored) {
+ }
+ return MISSING;
+ }
+
+ private static Method getter(Class<?> type, String methodName) {
+ try {
+ Method method = type.getMethod(methodName);
+ if (Modifier.isStatic(method.getModifiers()) ||
method.getReturnType() == void.class) {
+ return null;
+ }
+ makeAccessible(method);
+ return method;
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ private static boolean isRecord(Class<?> type) {
+ Class<?> superclass = type.getSuperclass();
+ return superclass != null &&
"java.lang.Record".equals(superclass.getName());
+ }
+
+ // Public members of non-public classes, such as a private nested class,
still need this.
+ private static void makeAccessible(java.lang.reflect.AccessibleObject
member) {
+ try {
+ member.setAccessible(true);
+ } catch (RuntimeException ignored) {
+ }
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
index 119c628..fec11c4 100644
--- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
@@ -20,11 +20,6 @@ package org.casbin.jcasbin.main;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.Expression;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorFunction;
import org.casbin.jcasbin.effect.DefaultEffector;
import org.casbin.jcasbin.effect.Effect;
import org.casbin.jcasbin.effect.Effector;
@@ -32,6 +27,8 @@ import org.casbin.jcasbin.effect.StreamEffector;
import org.casbin.jcasbin.exception.CasbinAdapterException;
import org.casbin.jcasbin.exception.CasbinEffectorException;
import org.casbin.jcasbin.exception.CasbinMatcherException;
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.model.Assertion;
import org.casbin.jcasbin.model.FunctionMap;
import org.casbin.jcasbin.model.Model;
@@ -55,6 +52,8 @@ import java.util.regex.Pattern;
public class CoreEnforcer {
private static final Pattern STRING_LITERAL =
Pattern.compile("'(?:\\\\.|[^'\\\\])*'|\"(?:\\\\.|[^\"\\\\])*\"");
private static final Pattern MATCHER_FUNCTION_CALL =
Pattern.compile("(?<![\\w.])(\\w+)\\s*\\(");
+ // A custom matcher may be written as a model line, such as "m = r.sub ==
p.sub".
+ private static final Pattern MATCHER_KEY_PREFIX =
Pattern.compile("^\\s*m\\w*\\s*=(?![=~])");
String modelPath;
Model model;
@@ -74,8 +73,9 @@ public class CoreEnforcer {
boolean autoNotifyDispatcher = true;
boolean acceptJsonRequest = false;
- private AviatorEvaluatorInstance aviatorEval;
- private final Map<String, Map<String, AviatorBoolean>> gFunctionCache =
new ConcurrentHashMap<>();
+ private static final int MAX_CACHED_EXPRESSIONS = 1000;
+ private final Map<String, Expression> expressionCache = new
ConcurrentHashMap<>();
+ private final Map<String, Map<String, Boolean>> gFunctionCache = new
ConcurrentHashMap<>();
void initialize() {
rmMap = new HashMap<>();
@@ -87,9 +87,7 @@ public class CoreEnforcer {
autoSave = adapter instanceof FileAdapter ? false : true;
autoBuildRoleLinks = true;
dispatcher = null;
- aviatorEval = AviatorEvaluator.newInstance();
initRmMap();
- initBuiltInFunction();
}
/**
@@ -167,24 +165,6 @@ public class CoreEnforcer {
fm = FunctionMap.loadFunctionMap();
}
- /**
- * set the aviator evaluator
- *
- * @param evaluator aviator evaluator
- */
- public void setAviatorEvaluator(AviatorEvaluatorInstance evaluator) {
- this.aviatorEval = Objects.requireNonNull(evaluator, "The aviator
evaluator cannot be null.");
- }
-
- /**
- * gets the current Aviator Evaluator instance
- *
- * @return Aviator Evaluator instance of enforcer
- */
- public AviatorEvaluatorInstance getAviatorEval() {
- return aviatorEval;
- }
-
/**
* getAdapter gets the current adapter.
*
@@ -464,17 +444,6 @@ public class CoreEnforcer {
registerDomainMatchingFunc(ptype);
}
- private void initBuiltInFunction() {
- for (Map.Entry<String, AviatorFunction> entry : fm.fm.entrySet()) {
- AviatorFunction function = entry.getValue();
-
- if (aviatorEval.containsFunction(function.getName())) {
- aviatorEval.removeFunction(function.getName());
- }
- aviatorEval.addFunction(function);
- }
- }
-
/**
* clearRmMap clears rmMap.
*/
@@ -574,13 +543,7 @@ public class CoreEnforcer {
return new EnforceResult(true, new
ArrayList<>(Collections.singletonList("The enforcer is not enabled, allow all
requests")));
}
- boolean compileCached = true;
- if (fm.isModify) {
- compileCached = false;
- initBuiltInFunction();
- fm.isModify = false;
- }
- Map<String, AviatorFunction> gFunctions = new HashMap<>();
+ Map<String, ExpressionFunction> functions = new HashMap<>(fm.fm);
if (model.model.containsKey("g")) {
for (Map.Entry<String, Assertion> entry :
model.model.get("g").entrySet()) {
String key = entry.getKey();
@@ -588,25 +551,16 @@ public class CoreEnforcer {
RoleManager rm = ast.rm;
if (rm != null){
- AviatorFunction aviatorFunction =
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm,
gFunctionCache);
- gFunctions.put(key, aviatorFunction);
+ functions.put(key,
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm,
gFunctionCache));
}
ConditionalRoleManager condRM = ast.condRM;
if (condRM != null){
- AviatorFunction aviatorFunction =
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
condRM, gFunctionCache);
- gFunctions.put(key, aviatorFunction);
+ functions.put(key,
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
condRM, gFunctionCache));
}
}
}
- for (AviatorFunction f : gFunctions.values()) {
- if (!aviatorEval.containsFunction(f.getName())) {
- aviatorEval.addFunction(f);
- compileCached = false;
- }
- }
- fm.setAviatorEval(aviatorEval);
String rType = "r", pType = "p", eType = "e", mType = "m";
if (rvals.length != 0) {
@@ -624,7 +578,7 @@ public class CoreEnforcer {
if (matcher == null || "".equals(matcher)) {
expString = model.model.get("m").get(mType).value;
} else {
- expString = Util.removeComments(Util.escapeAssertion(matcher));
+ expString =
Util.removeComments(Util.escapeAssertion(MATCHER_KEY_PREFIX.matcher(matcher).replaceFirst("")));
}
// json process
@@ -649,9 +603,7 @@ public class CoreEnforcer {
}
}
- expString = Util.convertInSyntax(expString);
- // Use md5 encryption as cacheKey to prevent expString from being too
long
- Expression expression = aviatorEval.compile(Util.md5(expString),
expString, compileCached);
+ Expression expression = compileMatcher(expString);
StreamEffector streamEffector = null;
try {
@@ -679,7 +631,7 @@ public class CoreEnforcer {
getPTokens(parameters, pType, pvals, pTokens);
getRTokens(parameters, rType, rvals);
- Object result = expression.execute(parameters);
+ Object result = expression.evaluate(parameters, functions);
if (result instanceof Boolean) {
if (!((boolean) result)) {
@@ -690,7 +642,7 @@ public class CoreEnforcer {
if (streamEffector == null) {
continue;
}
- } else if (result instanceof Double || result instanceof Long)
{
+ } else if (result instanceof Number) {
if (((Number) result).floatValue() == 0) {
policyEffects[i] = Effect.Indeterminate;
} else {
@@ -740,7 +692,7 @@ public class CoreEnforcer {
parameters.put(token, "");
}
- Object result = expression.execute(parameters);
+ Object result = expression.evaluate(parameters, functions);
if (streamEffector != null) {
if ((boolean) result) {
@@ -1010,7 +962,19 @@ public class CoreEnforcer {
* need to call it explicitly if you manipulate directly Model.
*/
public void resetExpressionEvaluator() {
- fm.setAviatorEval(null);
+ expressionCache.clear();
+ }
+
+ private Expression compileMatcher(String expString) {
+ Expression expression = expressionCache.get(expString);
+ if (expression == null) {
+ expression = Expression.compile(expString);
+ if (expressionCache.size() >= MAX_CACHED_EXPRESSIONS) {
+ expressionCache.clear();
+ }
+ expressionCache.put(expString, expression);
+ }
+ return expression;
}
public boolean isAutoNotifyWatcher() {
diff --git a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
index 663533d..c3cb17d 100644
--- a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
@@ -18,9 +18,9 @@
package org.casbin.jcasbin.main;
import org.casbin.jcasbin.effect.Effect;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.model.Assertion;
import org.casbin.jcasbin.util.Util;
-import org.casbin.jcasbin.util.function.CustomFunction;
import java.util.*;
import java.util.stream.Collectors;
@@ -629,7 +629,6 @@ public class ManagementEnforcer extends InternalEnforcer {
public boolean addNamedGroupingPolicy(String ptype, List<String> params) {
boolean ruleAdded = addPolicy("g", ptype, params);
- fm.setAviatorEval(null);
return ruleAdded;
}
@@ -784,7 +783,6 @@ public class ManagementEnforcer extends InternalEnforcer {
public boolean removeNamedGroupingPolicy(String ptype, List<String>
params) {
boolean ruleRemoved = removePolicy("g", ptype, params);
- fm.setAviatorEval(null);
return ruleRemoved;
}
@@ -833,7 +831,6 @@ public class ManagementEnforcer extends InternalEnforcer {
public boolean removeFilteredNamedGroupingPolicy(String ptype, int
fieldIndex, String... fieldValues) {
boolean ruleRemoved = removeFilteredPolicy("g", ptype, fieldIndex,
fieldValues);
- fm.setAviatorEval(null);
return ruleRemoved;
}
@@ -843,10 +840,8 @@ public class ManagementEnforcer extends InternalEnforcer {
* @param name the name of the function.
* @param function the custom function.
*/
- public void addFunction(String name, CustomFunction function) {
+ public void addFunction(String name, ExpressionFunction function) {
fm.addFunction(name, function);
-
- fm.setAviatorEval(null);
}
/**
diff --git a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
index 5651daa..7586663 100644
--- a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
+++ b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
@@ -17,8 +17,7 @@
package org.casbin.jcasbin.model;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.type.AviatorFunction;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.function.*;
import java.util.HashMap;
@@ -29,9 +28,9 @@ import java.util.Map;
*/
public class FunctionMap {
/**
- * AviatorFunction represents a function that is used in the matchers,
used to get attributes in ABAC.
+ * fm maps the function names to the functions that can be used in the
matchers.
*/
- public Map<String, AviatorFunction> fm;
+ public Map<String, ExpressionFunction> fm;
public boolean isModify = false;
@@ -41,36 +40,11 @@ public class FunctionMap {
* @param name the name of the new function.
* @param function the function.
*/
- public void addFunction(String name, AviatorFunction function) {
+ public void addFunction(String name, ExpressionFunction function) {
fm.put(name, function);
isModify=true;
}
- /**
- * setAviatorEval adds AviatorEvaluatorInstance to the custom function.
- *
- * @param name the name of the custom function.
- * @param aviatorEval the AviatorEvaluatorInstance object.
- */
- public void setAviatorEval(String name, AviatorEvaluatorInstance
aviatorEval) {
- if (fm.containsKey(name) && fm.get(name) instanceof CustomFunction) {
- ((CustomFunction) fm.get(name)).setAviatorEval(aviatorEval);
- }
- }
-
- /**
- * setAviatorEval adds AviatorEvaluatorInstance to all the custom function.
- *
- * @param aviatorEval the AviatorEvaluatorInstance object.
- */
- public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
- for (AviatorFunction function : fm.values()) {
- if (function instanceof CustomFunction) {
- ((CustomFunction) function).setAviatorEval(aviatorEval);
- }
- }
- }
-
/**
* loadFunctionMap loads an initial function map.
*
diff --git a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
index 318d5b5..09fe1d2 100644
--- a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
+++ b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
@@ -17,16 +17,15 @@
package org.casbin.jcasbin.util;
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.*;
import inet.ipaddr.AddressStringException;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.model.FunctionMap;
import org.casbin.jcasbin.rbac.ConditionalRoleManager;
import org.casbin.jcasbin.rbac.RoleManager;
+import org.casbin.jcasbin.util.function.FunctionArgs;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -41,6 +40,8 @@ public class BuiltInFunctions {
private static final Pattern KEY_MATCH2_PATTERN =
Pattern.compile(":[^/]+");
private static final Pattern KEY_MATCH3_PATTERN =
Pattern.compile("\\{[^/]+\\}");
private static final Pattern KEY_MATCH5_PATTERN =
Pattern.compile("\\{[^/]+\\}");
+ private static final int MAX_EVAL_EXPRESSIONS = 10000;
+ private static final Map<String, Expression> EVAL_EXPRESSIONS = new
ConcurrentHashMap<>();
/**
* validate the variadic string parameter size
@@ -407,7 +408,7 @@ public class BuiltInFunctions {
* @param rm the role manager used by the function.
* @return the function.
*/
- public static AviatorFunction generateGFunction(String name,
RoleManager rm) {
+ public static ExpressionFunction generateGFunction(String name,
RoleManager rm) {
return generateGFunction(name, rm, new ConcurrentHashMap<>());
}
@@ -419,49 +420,34 @@ public class BuiltInFunctions {
* @param memorizedMap the cache of g(_, _) results owned by the
caller, key: name such as g, g2.
* @return the function.
*/
- public static AviatorFunction generateGFunction(String name,
RoleManager rm, Map<String, Map<String, AviatorBoolean>> memorizedMap) {
+ public static ExpressionFunction generateGFunction(String name,
RoleManager rm, Map<String, Map<String, Boolean>> memorizedMap) {
memorizedMap.put(name, new ConcurrentHashMap<>());
- return new AbstractVariadicFunction() {
- @Override
- public AviatorObject variadicCall(Map<String, Object> env,
AviatorObject... args) {
- Map<String, AviatorBoolean> memorized =
memorizedMap.get(name);
- int len = args.length;
- if (len < 2) {
- return AviatorBoolean.valueOf(false);
- }
- String name1 = FunctionUtils.getStringValue(args[0], env);
- String name2 = FunctionUtils.getStringValue(args[1], env);
-
- String key = "";
- for (AviatorObject arg : args) {
- String name = FunctionUtils.getStringValue(arg, env);
- key += ";" + name;
- }
-
- AviatorBoolean value = memorized.get(key);
- if (value != null) {
- return value;
- }
-
- if (rm == null) {
- value = AviatorBoolean.valueOf(name1.equals(name2));
- } else if (len == 2) {
- value = AviatorBoolean.valueOf(rm.hasLink(name1,
name2));
- } else if (len >= 3) {
- String domain = FunctionUtils.getStringValue(args[2],
env);
- value = AviatorBoolean.valueOf(rm.hasLink(name1,
name2, domain));
- } else {
- value = AviatorBoolean.valueOf(false);
- }
- memorized.put(key, value);
+ return args -> {
+ Map<String, Boolean> memorized = memorizedMap.get(name);
+ int len = args.length;
+ if (len < 2) {
+ return false;
+ }
+ String name1 = FunctionArgs.getString(args, 0);
+ String name2 = FunctionArgs.getString(args, 1);
+
+ String key = gFunctionCacheKey(args);
+ Boolean value = memorized.get(key);
+ if (value != null) {
return value;
}
- @Override
- public String getName() {
- return name;
+ if (rm == null) {
+ value = name1.equals(name2);
+ } else if (len == 2) {
+ value = rm.hasLink(name1, name2);
+ } else {
+ String domain = FunctionArgs.getString(args, 2);
+ value = rm.hasLink(name1, name2, domain);
}
+ memorized.put(key, value);
+ return value;
};
}
}
@@ -474,7 +460,7 @@ public class BuiltInFunctions {
* @param condRm the conditional role manager used by the function.
* @return the function.
*/
- public static AviatorFunction generateConditionalGFunction(String
name, ConditionalRoleManager condRm) {
+ public static ExpressionFunction generateConditionalGFunction(String
name, ConditionalRoleManager condRm) {
return generateConditionalGFunction(name, condRm, new
ConcurrentHashMap<>());
}
@@ -486,85 +472,74 @@ public class BuiltInFunctions {
* @param memorizedMap the cache of g(_, _) results owned by the
caller, key: name such as g, g2.
* @return the function.
*/
- public static AviatorFunction generateConditionalGFunction(String
name, ConditionalRoleManager condRm, Map<String, Map<String, AviatorBoolean>>
memorizedMap) {
+ public static ExpressionFunction generateConditionalGFunction(String
name, ConditionalRoleManager condRm, Map<String, Map<String, Boolean>>
memorizedMap) {
memorizedMap.put(name, new ConcurrentHashMap<>());
- return new AbstractVariadicFunction() {
- @Override
- public AviatorObject variadicCall(Map<String, Object> env,
AviatorObject... args) {
- Map<String, AviatorBoolean> memorized =
memorizedMap.get(name);
- int len = args.length;
- if (len < 2) {
- return AviatorBoolean.valueOf(false);
- }
- String name1 = FunctionUtils.getStringValue(args[0], env);
- String name2 = FunctionUtils.getStringValue(args[1], env);
-
- String key = "";
- for (AviatorObject arg : args) {
- String name = FunctionUtils.getStringValue(arg, env);
- key += ";" + name;
- }
-
- AviatorBoolean value = memorized.get(key);
- if (value != null) {
- return value;
- }
-
- boolean hasLink;
- if (condRm == null) {
- hasLink = name1.equals(name2);
- } else if (len == 2) {
- hasLink = condRm.hasLink(name1, name2);
- } else if (len >= 3) {
- String domain = FunctionUtils.getStringValue(args[2],
env);
- hasLink = condRm.hasLink(name1, name2, domain);
- } else {
- hasLink = false;
- }
- value = AviatorBoolean.valueOf(hasLink);
- memorized.put(key, value);
+ return args -> {
+ Map<String, Boolean> memorized = memorizedMap.get(name);
+ int len = args.length;
+ if (len < 2) {
+ return false;
+ }
+ String name1 = FunctionArgs.getString(args, 0);
+ String name2 = FunctionArgs.getString(args, 1);
+
+ String key = gFunctionCacheKey(args);
+ Boolean value = memorized.get(key);
+ if (value != null) {
return value;
}
- @Override
- public String getName() {
- return name;
+ if (condRm == null) {
+ value = name1.equals(name2);
+ } else if (len == 2) {
+ value = condRm.hasLink(name1, name2);
+ } else {
+ String domain = FunctionArgs.getString(args, 2);
+ value = condRm.hasLink(name1, name2, domain);
}
+ memorized.put(key, value);
+ return value;
};
}
}
+ private static String gFunctionCacheKey(Object[] args) {
+ StringBuilder key = new StringBuilder();
+ for (int i = 0; i < args.length; i++) {
+ key.append(';').append(FunctionArgs.getString(args, i));
+ }
+ return key.toString();
+ }
+
/**
* eval calculates the stringified boolean expression and return its
result.
*
- * @param eval the stringified boolean expression.
- * @param env the key-value pair of the parameters in the
expression.
- * @param aviatorEval the AviatorEvaluatorInstance object which contains
built-in functions and custom functions.
- * @return the result of the eval.
+ * @param eval the stringified boolean expression.
+ * @param env the key-value pair of the parameters in the expression.
+ * @param functions the built-in functions and custom functions the
expression can call,
+ * null means only the built-in functions.
+ * @return the result of the eval, false if the expression is invalid or
fails.
*/
- public static boolean eval(String eval, Map<String, Object> env,
AviatorEvaluatorInstance aviatorEval) {
- // Validate expression to block AviatorScript-specific features
- // that break cross-platform compatibility
- try {
- ExpressionValidator.validateExpression(eval);
- } catch (IllegalArgumentException e) {
- Util.logPrintfWarn("Expression validation failed: {}",
e.getMessage());
- return false;
+ public static boolean eval(String eval, Map<String, Object> env,
Map<String, ? extends ExpressionFunction> functions) {
+ if (functions == null) {
+ functions = FunctionMap.loadFunctionMap().fm;
}
-
- boolean res;
- if (aviatorEval != null) {
- try {
- res = (boolean) aviatorEval.execute(eval, env);
- } catch (Exception e) {
- Util.logPrintfWarn("Execute 'eval' function error, nested
exception is: {}", e.getMessage());
- res = false;
+ try {
+ Expression expression = EVAL_EXPRESSIONS.get(eval);
+ if (expression == null) {
+ expression = Expression.compile(eval);
+ if (EVAL_EXPRESSIONS.size() >= MAX_EVAL_EXPRESSIONS) {
+ EVAL_EXPRESSIONS.clear();
+ }
+ EVAL_EXPRESSIONS.put(eval, expression);
}
- } else {
- res = (boolean) AviatorEvaluator.execute(eval, env);
+ Object res = expression.evaluate(env, functions);
+ return res instanceof Boolean && (Boolean) res;
+ } catch (Exception e) {
+ Util.logPrintfWarn("Execute 'eval' function error, nested
exception is: {}", e.getMessage());
+ return false;
}
- return res;
}
// builtin LinkConditionFunc
diff --git a/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java
b/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java
deleted file mode 100644
index c91d280..0000000
--- a/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.casbin.jcasbin.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * ExpressionValidator validates expressions to ensure they only use standard
Casbin syntax
- * and don't expose AviatorScript-specific features that would break
cross-platform compatibility.
- */
-public class ExpressionValidator {
-
- // Patterns for AviatorScript-specific syntax that should be blocked
- private static final Pattern[] DISALLOWED_PATTERNS = {
- Pattern.compile("\\bseq\\."), // seq.list(), seq.map(), etc.
- Pattern.compile("\\bstring\\."), // string.startsWith(),
string.endsWith(), etc.
- Pattern.compile("\\bmath\\."), // math.sqrt(), math.pow(),
etc.
- Pattern.compile("\\blambda\\b"), // lambda expressions
- Pattern.compile("\\blet\\b"), // variable binding
- Pattern.compile("\\bfn\\b"), // function definitions
- Pattern.compile("->"), // lambda arrow
- Pattern.compile("=>"), // alternative lambda arrow
- Pattern.compile("\\bfor\\b"), // for loops
- Pattern.compile("\\bwhile\\b"), // while loops
- Pattern.compile("\\breturn\\b"), // return statements
- Pattern.compile("\\bif\\b.*\\bthen\\b.*\\belse\\b"), // if-then-else
(aviator style)
- };
-
- /**
- * Validates that an expression only uses standard Casbin syntax.
- *
- * @param expression the expression to validate
- * @throws IllegalArgumentException if the expression contains
non-standard syntax
- */
- public static void validateExpression(String expression) {
- if (expression == null || expression.trim().isEmpty()) {
- return;
- }
-
- // Check for disallowed AviatorScript-specific patterns
- for (Pattern pattern : DISALLOWED_PATTERNS) {
- Matcher matcher = pattern.matcher(expression);
- if (matcher.find()) {
- throw new IllegalArgumentException(
- "Expression contains non-standard syntax: '" +
matcher.group() +
- "'. This AviatorScript-specific feature is not part of
Casbin's standard specification."
- );
- }
- }
- }
-}
diff --git a/src/main/java/org/casbin/jcasbin/util/Util.java
b/src/main/java/org/casbin/jcasbin/util/Util.java
index fb52d0f..0cc61e8 100644
--- a/src/main/java/org/casbin/jcasbin/util/Util.java
+++ b/src/main/java/org/casbin/jcasbin/util/Util.java
@@ -38,8 +38,6 @@ public class Util {
public static boolean enableLog = true;
private static Pattern evalReg = Pattern.compile("\\beval\\(([^),]*)\\)");
- private static final Pattern IN_SYNTAX_PATTERN =
Pattern.compile("([^\\s]+?) +in +(\\([^)]*\\)|\\[[^\\]]*\\]|\\S+)");
-
private static Pattern escapeAssertionRegex =
Pattern.compile("\\b(r|p)[0-9]*\\.");
private static Logger LOGGER =
LoggerFactory.getLogger("org.casbin.jcasbin");
@@ -189,44 +187,6 @@ public class Util {
return sb.toString();
}
- /**
- * convertInSyntax Convert 'in' to 'include' to fit aviatorscript,because
- * aviatorscript don't support native 'in' syntax
- *
- * @param expString the value of the matcher
- * @return the 'include' expression.
- */
- public static String convertInSyntax(String expString) {
- if (expString.contains(" in ")) {
- Matcher matcher = IN_SYNTAX_PATTERN.matcher(expString);
- if (matcher.find()) {
- return replaceInSyntaxWithInclude(matcher);
- }
- }
-
- return expString;
- }
-
- private static String replaceInSyntaxWithInclude(Matcher matcher) {
- StringBuffer sb = new StringBuffer();
- do {
- String leftOperand = matcher.group(1);
- String rightOperand = matcher.group(2);
-
- // If right operand is a parenthesized expression with commas
(tuple literal),
- // convert to tuple() function. Otherwise, keep parentheses as-is
for grouping.
- if (rightOperand.startsWith("(") && rightOperand.endsWith(")") &&
rightOperand.contains(",")) {
- rightOperand = "tuple" + rightOperand;
- } else if (rightOperand.startsWith("[") &&
rightOperand.endsWith("]")) {
- rightOperand = "tuple(" + rightOperand.substring(1,
rightOperand.length() - 1) + ")";
- }
-
- matcher.appendReplacement(sb, Matcher.quoteReplacement("include("
+ rightOperand + ", " + leftOperand + ")"));
- } while (matcher.find());
- matcher.appendTail(sb);
- return sb.toString();
- }
-
/**
* removeComments removes the comments starting with # in the text.
*
diff --git a/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
b/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
index 298d039..a990003 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
@@ -17,34 +17,20 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-
-import java.util.Map;
+import org.casbin.jcasbin.expression.ExpressionFunction;
/**
+ * CustomFunction is the base class of named functions added with
addFunction().
+ * Implement call(Object... args); the arguments are already evaluated, and
+ * FunctionArgs helps to check and read them.
+ *
* @author: shink
*/
-public abstract class CustomFunction extends AbstractFunction {
-
- private AviatorEvaluatorInstance aviatorEval;
-
- public String replaceTargets(String exp, Map<String, Object> env) {
- //Replace the first dot, because it can't be recognized by the 'reg'
below.
- if (exp.startsWith( "r") || exp.startsWith( "p")) {
- exp = exp.replaceFirst("\\.","_");
- }
- //match example: "&&r.","||r.","=r."
- String reg = "([| =)(&<>,+\\-*/!])((r|p)[0-9]*)\\.";
- exp = exp.replaceAll(reg,"$1$2_");
- return exp;
- }
-
- public AviatorEvaluatorInstance getAviatorEval() {
- return aviatorEval;
- }
-
- public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
- this.aviatorEval = aviatorEval;
- }
+public abstract class CustomFunction implements ExpressionFunction {
+ /**
+ * getName returns the name the function is called by in matchers.
+ *
+ * @return the function name.
+ */
+ public abstract String getName();
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
index 2889fcb..62f9f62 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
@@ -17,29 +17,33 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.EvaluationContext;
+import org.casbin.jcasbin.expression.ExpressionException;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-
-import java.util.Map;
+import org.casbin.jcasbin.util.Util;
/**
* EvalFunc is the wrapper for eval.
- * It extends CustomFunction, so it can be used in matcher and policy rule.
+ * It evaluates a rule stored in the policy, such as "r.sub.Age > 18",
+ * with the variables and functions of the current matcher.
*
* @author shink
*/
-public class EvalFunc extends CustomFunction {
+public class EvalFunc implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
- String eval = FunctionUtils.getStringValue(arg1, env);
- eval = replaceTargets(eval, env);
- return AviatorBoolean.valueOf(BuiltInFunctions.eval(eval, env,
getAviatorEval()));
+ public Object call(Object... args) {
+ throw new ExpressionException("eval can only be called from a
matcher");
}
@Override
+ public Object call(EvaluationContext context, Object... args) {
+ FunctionArgs.checkCount("eval", args, 1);
+ String eval = Util.escapeAssertion(FunctionArgs.getString(args, 0));
+ return BuiltInFunctions.eval(eval, context.getVariables(),
context.getFunctions());
+ }
+
public String getName() {
return "eval";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java
b/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java
new file mode 100644
index 0000000..3e0f00a
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.util.function;
+
+import org.casbin.jcasbin.expression.ExpressionException;
+
+/**
+ * FunctionArgs checks the arguments passed to matcher functions.
+ */
+public final class FunctionArgs {
+ private FunctionArgs() {
+ }
+
+ /**
+ * checkCount checks the number of arguments.
+ *
+ * @param name the function name, used in the error message.
+ * @param args the arguments.
+ * @param expected the expected number of arguments.
+ */
+ public static void checkCount(String name, Object[] args, int expected) {
+ if (args.length != expected) {
+ throw new ExpressionException("wrong number of arguments (" +
args.length + ") passed to " + name + ", expected " + expected);
+ }
+ }
+
+ /**
+ * getString returns the argument at index as a string.
+ *
+ * @param args the arguments.
+ * @param index the index of the argument.
+ * @return the string value.
+ */
+ public static String getString(Object[] args, int index) {
+ Object value = args[index];
+ if (value instanceof String) {
+ return (String) value;
+ }
+ if (value instanceof Character) {
+ return value.toString();
+ }
+ throw new ExpressionException("argument " + (index + 1) + " must be a
string, got "
+ + (value == null ? "null" : value + " (" +
value.getClass().getSimpleName() + ")"));
+ }
+}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
index 1dcbfbd..57e798e 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
@@ -17,30 +17,25 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* GlobMatchFunc is the wrapper for globMatch.
*
* @author shy
* @since 2021/1/12
*/
-public class GlobMatchFunc extends AbstractFunction {
+public class GlobMatchFunc implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("globMatch", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.globMatch(key1, key2));
+ return BuiltInFunctions.globMatch(key1, key2);
}
- @Override
public String getName() {
return "globMatch";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
index 5d32c6d..ca6d583 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
@@ -17,27 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* IPMatchFunc is the wrapper for ipMatch.
*/
-public class IPMatchFunc extends AbstractFunction {
+public class IPMatchFunc implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String ip1 = FunctionUtils.getStringValue(arg1, env);
- String ip2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("ipMatch", args, 2);
+ String ip1 = FunctionArgs.getString(args, 0);
+ String ip2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.ipMatch(ip1, ip2));
+ return BuiltInFunctions.ipMatch(ip1, ip2);
}
- @Override
public String getName() {
return "ipMatch";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
index 78549fb..cf33e8f 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
@@ -17,31 +17,26 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyGet2Func is the wrapper for keyGet2.
*
* @author shy
* @since 2021/02/09
*/
-public class KeyGet2Func extends AbstractFunction {
+public class KeyGet2Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2, AviatorObject arg3) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
- String pathVar = FunctionUtils.getStringValue(arg3, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyGet2", args, 3);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
+ String pathVar = FunctionArgs.getString(args, 2);
- return new AviatorString(BuiltInFunctions.keyGet2Func(key1, key2,
pathVar));
+ return BuiltInFunctions.keyGet2Func(key1, key2, pathVar);
}
- @Override
public String getName() {
return "keyGet2";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
index 293b93c..c3ecc97 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
@@ -17,31 +17,26 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyGet3Func is the wrapper for keyGet3.
*
* @author yanglif
* @since 2021/02/09
*/
-public class KeyGet3Func extends AbstractFunction {
+public class KeyGet3Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2, AviatorObject arg3) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
- String pathVar = FunctionUtils.getStringValue(arg3, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyGet3", args, 3);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
+ String pathVar = FunctionArgs.getString(args, 2);
- return new AviatorString(BuiltInFunctions.keyGet3Func(key1, key2,
pathVar));
+ return BuiltInFunctions.keyGet3Func(key1, key2, pathVar);
}
- @Override
public String getName() {
return "keyGet3";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
index 5a73906..b6bb183 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
@@ -17,30 +17,25 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyGetFunc is the wrapper for keyGet.
*
* @author shy
* @since 2021/02/09
*/
-public class KeyGetFunc extends AbstractFunction {
+public class KeyGetFunc implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyGet", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return new AviatorString(BuiltInFunctions.keyGetFunc(key1, key2));
+ return BuiltInFunctions.keyGetFunc(key1, key2);
}
- @Override
public String getName() {
return "keyGet";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
index 2c62105..e9624eb 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
@@ -17,27 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyMatch2Func is the wrapper for keyMatch2.
*/
-public class KeyMatch2Func extends AbstractFunction {
+public class KeyMatch2Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyMatch2", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch2(key1, key2));
+ return BuiltInFunctions.keyMatch2(key1, key2);
}
- @Override
public String getName() {
return "keyMatch2";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
index ac33e11..917cdd2 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
@@ -17,27 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyMatch3Func is the wrapper for keyMatch3.
*/
-public class KeyMatch3Func extends AbstractFunction {
+public class KeyMatch3Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyMatch3", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch3(key1, key2));
+ return BuiltInFunctions.keyMatch3(key1, key2);
}
- @Override
public String getName() {
return "keyMatch3";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
index 432e13b..11f63d6 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
@@ -17,27 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyMatch4Func is the wrapper for keyMatch4.
*/
-public class KeyMatch4Func extends AbstractFunction {
+public class KeyMatch4Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyMatch4", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch4(key1, key2));
+ return BuiltInFunctions.keyMatch4(key1, key2);
}
- @Override
public String getName() {
return "keyMatch4";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
index 99c440c..5fe6fc1 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
@@ -16,30 +16,25 @@
// under the License.
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyMatch5Func is the wrapper for KeyMatch5.
* @author Yixiang Zhao (@seriouszyx)
* 2021-11-13 20:32
**/
-public class KeyMatch5Func extends AbstractFunction {
+public class KeyMatch5Func implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyMatch5", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch5(key1, key2));
+ return BuiltInFunctions.keyMatch5(key1, key2);
}
- @Override
public String getName() {
return "keyMatch5";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
index 3acae6e..d18521d 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
@@ -17,27 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* KeyMatchFunc is the wrapper for keyMatch.
*/
-public class KeyMatchFunc extends AbstractFunction {
+public class KeyMatchFunc implements ExpressionFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("keyMatch", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch(key1, key2));
+ return BuiltInFunctions.keyMatch(key1, key2);
}
- @Override
public String getName() {
return "keyMatch";
}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
b/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
index cdd8f65..4a25426 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
@@ -17,26 +17,22 @@
package org.casbin.jcasbin.util.function;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
-import java.util.Map;
-
/**
* RegexMatchFunc is the wrapper for regexMatch.
*/
-public class RegexMatchFunc extends AbstractFunction {
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+public class RegexMatchFunc implements ExpressionFunction {
+ @Override
+ public Object call(Object... args) {
+ FunctionArgs.checkCount("regexMatch", args, 2);
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(BuiltInFunctions.regexMatch(key1, key2));
+ return BuiltInFunctions.regexMatch(key1, key2);
}
- @Override
public String getName() {
return "regexMatch";
}
diff --git
a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
index a341b60..8e200b6 100644
--- a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
@@ -17,8 +17,8 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.model.FunctionMap;
import org.casbin.jcasbin.util.BuiltInFunctions;
import org.casbin.jcasbin.util.Util;
import org.testng.annotations.Test;
@@ -291,11 +291,11 @@ public class BuiltInFunctionsUnitTest {
testEval("r_sub.name == 'alice'", env, null, true);
testEval("r_sub.name == 'bob'", env, null, false);
- AviatorEvaluatorInstance aviatorEval = AviatorEvaluator.newInstance();
- aviatorEval.addFunction(new FunctionTest.CustomFunc());
+ Map<String, ExpressionFunction> functions =
FunctionMap.loadFunctionMap().fm;
+ functions.put("custom", new FunctionTest.CustomFunc());
env.put("r_obj", "/test/url1/url2");
- testEval("r_sub.age >= 18 && custom(r_obj)", env, aviatorEval, true);
+ testEval("r_sub.age >= 18 && custom(r_obj)", env, functions, true);
}
@Test
@@ -344,13 +344,12 @@ public class BuiltInFunctionsUnitTest {
@Test
public void should_logged_when_eval_given_errorExpression() {
// given
- AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance();
Map<String, Object> env = new HashMap<>();
try (MockedStatic<Util> utilMocked =
BDDMockito.mockStatic(Util.class)) {
utilMocked.when(() -> Util.logPrintfWarn(anyString(),
anyString())).thenCallRealMethod();
// when
- BuiltInFunctions.eval("error", env, instance);
+ BuiltInFunctions.eval("error(", env, null);
// then
utilMocked.verify(() -> Util.logPrintfWarn(eq("Execute 'eval'
function error, nested exception is: {}"), any()));
}
diff --git a/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java
b/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java
new file mode 100644
index 0000000..82c1858
--- /dev/null
+++ b/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java
@@ -0,0 +1,228 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.main;
+
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionException;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.util.BuiltInFunctions;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.testng.Assert.*;
+
+public class ExpressionTest {
+ public static class Subject {
+ public String Department = "dev";
+ private final String name;
+ private final int age;
+ private final boolean admin;
+
+ Subject(String name, int age, boolean admin) {
+ this.name = name;
+ this.age = age;
+ this.admin = admin;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public boolean isAdmin() {
+ return admin;
+ }
+
+ public void delete() {
+ throw new IllegalStateException("must not be called");
+ }
+ }
+
+ private static Object eval(String expression, Map<String, Object>
variables) {
+ return Expression.compile(expression).evaluate(variables,
Collections.<String, ExpressionFunction>emptyMap());
+ }
+
+ private static Object eval(String expression) {
+ return eval(expression, Collections.emptyMap());
+ }
+
+ private static Map<String, Object> vars(Object... pairs) {
+ Map<String, Object> m = new HashMap<>();
+ for (int i = 0; i < pairs.length; i += 2) {
+ m.put((String) pairs[i], pairs[i + 1]);
+ }
+ return m;
+ }
+
+ @Test
+ public void testLiterals() {
+ assertEquals(eval("'a\\'b'"), "a'b");
+ assertEquals(eval("\"x\\ty\""), "x\ty");
+ assertEquals(eval("'^/foo\\d+$'"), "^/foo\\d+$");
+ assertEquals(eval("42"), 42L);
+ assertEquals(eval("1.5"), 1.5);
+ assertEquals(eval("true"), true);
+ assertNull(eval("nil"));
+ assertEquals(eval("('a', 'b')"), Arrays.asList("a", "b"));
+ assertEquals(eval("['a']"), Collections.singletonList("a"));
+ assertEquals(eval("()"), Collections.emptyList());
+ }
+
+ @Test
+ public void testArithmetic() {
+ assertEquals(eval("1 + 2 * 3"), 7L);
+ assertEquals(eval("(1 + 2) * 3"), 9L);
+ assertEquals(eval("10 - 4 - 3"), 3L);
+ assertEquals(eval("10 / 4"), 2.5);
+ assertEquals(eval("10 / 5"), 2.0);
+ assertEquals(eval("10 / 4.0"), 2.5);
+ assertEquals(eval("7 % 3"), 1L);
+ // Same as Go: ** is left-associative and binds looser than unary
minus.
+ assertEquals(eval("2 ** 3 ** 2"), 64L);
+ assertEquals(eval("-2 ** 2"), 4L);
+ assertEquals(eval("'a' + 1"), "a1");
+ assertEquals(eval("1 / 0"), Double.POSITIVE_INFINITY);
+ assertThrows(ExpressionException.class, () -> eval("'a' - 1"));
+ }
+
+ @Test
+ public void testPrecedence() {
+ // Same as Go: & before ^ before |, and all of them before comparisons.
+ assertEquals(eval("r_bits | 2 & 4", vars("r_bits", 1L)), 1L);
+ assertEquals(eval("1 ^ 3 & 1"), 0L);
+ assertEquals(eval("5 & 4 == 4"), true);
+ assertEquals(eval("true || false && false"), true);
+ assertEquals(eval("1 + 1 == 2 && 'a' < 'b'"), true);
+ // 'in' binds like ==, so the guard before it still applies.
+ assertEquals(eval("r_sub == 'bob' && r_public in (false)",
vars("r_sub", "alice", "r_public", false)), false);
+ assertEquals(eval("r_age > 18 ? 'adult' : 'minor'", vars("r_age",
20)), "adult");
+ assertEquals(eval("r_name ?? 'anonymous'"), "anonymous");
+ }
+
+ @Test
+ public void testComparison() {
+ assertEquals(eval("r_age >= 18", vars("r_age", 18)), true);
+ assertEquals(eval("r_age == 18", vars("r_age", 18)), true);
+ assertEquals(eval("r_age == 18.0", vars("r_age", 18)), true);
+ assertEquals(eval("r_score < 1.5", vars("r_score", 1.25f)), true);
+ assertEquals(eval("'1' == 1"), false);
+ assertEquals(eval("r_x == nil"), true);
+ assertEquals(eval("r_x != 'a'"), true);
+ assertEquals(eval("'abc' < 'abd'"), true);
+ assertThrows(ExpressionException.class, () -> eval("'a' < 1"));
+ assertThrows(ExpressionException.class, () -> eval("r_x < 1"));
+ }
+
+ @Test
+ public void testIn() {
+ assertEquals(eval("r_obj in ('data2', 'data3')", vars("r_obj",
"data3")), true);
+ assertEquals(eval("r_obj in ['data2', 'data3']", vars("r_obj",
"data1")), false);
+ assertEquals(eval("'bob' in r_admins", vars("r_admins",
Arrays.asList("alice", "bob"))), true);
+ assertEquals(eval("2 in r_ids", vars("r_ids", new int[]{1, 2})), true);
+ assertEquals(eval("'x' in r_ids", vars("r_ids", new String[]{"y"})),
false);
+ assertEquals(eval("!('a' in ('b'))"), true);
+ assertThrows(ExpressionException.class, () -> eval("'a' in 'abc'"));
+ }
+
+ @Test
+ public void testRegex() {
+ assertEquals(eval("r_path =~ '^/api/v[0-9]+/'", vars("r_path",
"/api/v2/users")), true);
+ assertEquals(eval("r_path !~ 'admin'", vars("r_path",
"/api/v2/users")), true);
+ assertThrows(ExpressionException.class, () -> eval("'a' =~ '('"));
+ }
+
+ @Test
+ public void testProperties() {
+ Subject alice = new Subject("alice", 20, true);
+ Map<String, Object> v = vars("r_sub", alice, "r_obj", vars("Owner",
"alice", "Meta", vars("level", 3)));
+ assertEquals(eval("r_sub.name", v), "alice");
+ assertEquals(eval("r_sub.Name", v), "alice");
+ assertEquals(eval("r_sub.age > 18", v), true);
+ assertEquals(eval("r_sub.admin", v), true);
+ assertEquals(eval("r_sub.Department", v), "dev");
+ assertEquals(eval("r_sub.name == r_obj.Owner", v), true);
+ assertEquals(eval("r_obj.Meta.level >= 3", v), true);
+ assertNull(eval("r_obj.Missing", v));
+ assertNull(eval("r_none.name", v));
+ assertThrows(ExpressionException.class, () -> eval("r_sub.class", v));
+ assertThrows(ExpressionException.class, () -> eval("r_sub.delete", v));
+ assertThrows(ExpressionException.class, () -> eval("r_sub.missing",
v));
+ }
+
+ @Test
+ public void testFunctions() {
+ Map<String, ExpressionFunction> functions = new HashMap<>();
+ functions.put("concat", args -> "" + args[0] + args[1]);
+ Expression expression = Expression.compile("concat(r_sub, 'x') == 'ax'
&& concat('(', ')') == '()'");
+ assertEquals(expression.evaluate(vars("r_sub", "a"), functions), true);
+ assertThrows(ExpressionException.class, () -> eval("unknown(1)"));
+ }
+
+ @Test
+ public void testShortCircuit() {
+ Map<String, ExpressionFunction> functions = new HashMap<>();
+ functions.put("fail", args -> {
+ throw new IllegalStateException("must not be called");
+ });
+ assertEquals(Expression.compile("false && fail()").evaluate(vars(),
functions), false);
+ assertEquals(Expression.compile("true || fail()").evaluate(vars(),
functions), true);
+ assertThrows(ExpressionException.class, () -> eval("1 && true"));
+ }
+
+ @Test
+ public void testSyntaxErrors() {
+ String[] invalid = {
+ "", "r_sub ==", "(r_sub", "r_sub)", "'unterminated", "r_sub =
'a'", "r_sub in", "1 2",
+ // AviatorScript syntax that is not part of Casbin.
+ "seq.list('A', 'B')",
+ "seq.map(r_items, lambda(x) -> x * 2)",
+ "string.startsWith(r_path, '/admin')",
+ "math.sqrt(r_value) > 10",
+ "lambda(x) -> x * 2",
+ "(x) => x * 2",
+ "let x = 1",
+ "for x in r_items { x * 2 }",
+ "while x < 10 { x = x + 1 }",
+ "if r_age > 18 then 'adult' else 'minor'",
+ "use java.util.*",
+ "r_sub.getName()",
+ };
+ for (String expression : invalid) {
+ try {
+ Expression.compile(expression);
+ fail("expected a syntax error: " + expression);
+ } catch (ExpressionException expected) {
+ }
+ }
+ }
+
+ @Test
+ public void testEvalRejectsInvalidRules() {
+ Map<String, Object> env = vars("r_path", "/admin", "r_value", 200);
+ assertFalse(BuiltInFunctions.eval("string.startsWith(r_path,
'/admin')", env, null));
+ assertFalse(BuiltInFunctions.eval("math.sqrt(r_value) > 10", env,
null));
+ assertTrue(BuiltInFunctions.eval("keyMatch(r_path, '/admin*') &&
r_value > 10", env, null));
+ }
+}
diff --git a/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java
b/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java
deleted file mode 100644
index 182c9bb..0000000
--- a/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.casbin.jcasbin.main;
-
-import org.casbin.jcasbin.util.ExpressionValidator;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.*;
-
-public class ExpressionValidatorTest {
-
- @Test
- public void testValidStandardCasbinExpressions() {
- // Standard operators and comparisons should be allowed
- ExpressionValidator.validateExpression("r_sub == p_sub");
- ExpressionValidator.validateExpression("r_sub == p_sub && r_obj ==
p_obj");
- ExpressionValidator.validateExpression("r_sub == p_sub || r_obj ==
p_obj");
- ExpressionValidator.validateExpression("r_age > 18");
- ExpressionValidator.validateExpression("r_age >= 18 && r_age < 65");
- ExpressionValidator.validateExpression("r_status != 'banned'");
-
- // Arithmetic should be allowed
- ExpressionValidator.validateExpression("r_price * 1.1 > p_threshold");
- ExpressionValidator.validateExpression("r_count + p_offset < 100");
- ExpressionValidator.validateExpression("r_value - p_discount >= 0");
- ExpressionValidator.validateExpression("r_total / r_count > 50");
-
- // Negation should be allowed
- ExpressionValidator.validateExpression("!r_disabled");
- ExpressionValidator.validateExpression("!(r_sub == p_sub)");
- }
-
- @Test
- public void testValidCasbinBuiltInFunctions() {
- // All standard Casbin functions should be allowed
- ExpressionValidator.validateExpression("g(r_sub, p_sub)");
- ExpressionValidator.validateExpression("g2(r_sub, p_sub, r_domain)");
- ExpressionValidator.validateExpression("keyMatch(r_path, p_path)");
- ExpressionValidator.validateExpression("keyMatch2(r_path, p_path)");
- ExpressionValidator.validateExpression("keyMatch3(r_path, p_path)");
- ExpressionValidator.validateExpression("keyMatch4(r_path, p_path)");
- ExpressionValidator.validateExpression("keyMatch5(r_path, p_path)");
- ExpressionValidator.validateExpression("keyGet(r_path, p_path)");
- ExpressionValidator.validateExpression("keyGet2(r_path, p_path,
'id')");
- ExpressionValidator.validateExpression("regexMatch(r_path,
p_pattern)");
- ExpressionValidator.validateExpression("ipMatch(r_ip, p_cidr)");
- ExpressionValidator.validateExpression("globMatch(r_path, p_glob)");
- ExpressionValidator.validateExpression("allMatch(r_key, p_key)");
- ExpressionValidator.validateExpression("timeMatch(r_time, p_time)");
- ExpressionValidator.validateExpression("eval(p_rule)");
-
- // Include and tuple are used for "in" operator conversion
- ExpressionValidator.validateExpression("include(r_obj, r_sub)");
- ExpressionValidator.validateExpression("include(tuple('admin',
'editor'), r_role)");
-
- // Custom functions should be allowed (users can register them)
- ExpressionValidator.validateExpression("customFunc(r_sub, p_sub)");
- ExpressionValidator.validateExpression("myFunction(r_value)");
- }
-
- @Test
- public void testValidComplexExpressions() {
- // Complex combinations should be allowed
- ExpressionValidator.validateExpression("g(r_sub, p_sub) && r_obj ==
p_obj && r_act == p_act");
- ExpressionValidator.validateExpression("g(r_sub, p_sub) &&
keyMatch(r_path, p_path)");
- ExpressionValidator.validateExpression("eval(p_sub_rule) && r_obj ==
p_obj");
- ExpressionValidator.validateExpression("r_age > 18 &&
include(tuple('read', 'write'), r_act)");
- ExpressionValidator.validateExpression("r_sub.age >= 18 &&
custom(r_obj)");
- }
-
- @Test
- public void testDisallowedAviatorScriptSequenceMethods() {
- // seq.list() should be disallowed
- try {
- ExpressionValidator.validateExpression("seq.list('A', 'B')");
- fail("Should have thrown IllegalArgumentException for seq.list()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("seq."));
- assertTrue(e.getMessage().contains("AviatorScript-specific"));
- }
-
- // seq.map() should be disallowed
- try {
- ExpressionValidator.validateExpression("seq.map(r_items, lambda(x)
-> x * 2)");
- fail("Should have thrown IllegalArgumentException for seq.map()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("seq."));
- }
- }
-
- @Test
- public void testDisallowedAviatorScriptStringMethods() {
- // string.startsWith() should be disallowed
- try {
- ExpressionValidator.validateExpression("string.startsWith(r_path,
'/admin')");
- fail("Should have thrown IllegalArgumentException for
string.startsWith()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("string."));
- assertTrue(e.getMessage().contains("AviatorScript-specific"));
- }
-
- // string.endsWith() should be disallowed
- try {
- ExpressionValidator.validateExpression("string.endsWith(r_path,
'.pdf')");
- fail("Should have thrown IllegalArgumentException for
string.endsWith()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("string."));
- }
-
- // string.substring() should be disallowed
- try {
- ExpressionValidator.validateExpression("string.substring(r_path,
0, 5)");
- fail("Should have thrown IllegalArgumentException for
string.substring()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("string."));
- }
- }
-
- @Test
- public void testDisallowedAviatorScriptMathMethods() {
- // math.sqrt() should be disallowed
- try {
- ExpressionValidator.validateExpression("math.sqrt(r_value) > 10");
- fail("Should have thrown IllegalArgumentException for
math.sqrt()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("math."));
- assertTrue(e.getMessage().contains("AviatorScript-specific"));
- }
-
- // math.pow() should be disallowed
- try {
- ExpressionValidator.validateExpression("math.pow(r_base, 2)");
- fail("Should have thrown IllegalArgumentException for math.pow()");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("math."));
- }
- }
-
- @Test
- public void testDisallowedLambdaExpressions() {
- // Lambda with arrow should be disallowed
- try {
- ExpressionValidator.validateExpression("lambda(x) -> x * 2");
- fail("Should have thrown IllegalArgumentException for lambda");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("lambda") ||
e.getMessage().contains("->"));
- }
-
- // Alternative lambda syntax should be disallowed
- try {
- ExpressionValidator.validateExpression("(x) => x * 2");
- fail("Should have thrown IllegalArgumentException for lambda
arrow");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("=>"));
- }
- }
-
- @Test
- public void testDisallowedControlStructures() {
- // for loops should be disallowed
- try {
- ExpressionValidator.validateExpression("for x in r_items { x * 2
}");
- fail("Should have thrown IllegalArgumentException for 'for'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("for"));
- }
-
- // while loops should be disallowed
- try {
- ExpressionValidator.validateExpression("while x < 10 { x = x + 1
}");
- fail("Should have thrown IllegalArgumentException for 'while'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("while"));
- }
-
- // if-then-else (Aviator style) should be disallowed
- try {
- ExpressionValidator.validateExpression("if r_age > 18 then 'adult'
else 'minor'");
- fail("Should have thrown IllegalArgumentException for
'if-then-else'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("if") ||
e.getMessage().contains("then") || e.getMessage().contains("else"));
- }
- }
-
- @Test
- public void testDisallowedVariableBindingAndFunctions() {
- // let variable binding should be disallowed
- try {
- ExpressionValidator.validateExpression("let x = 10; x * 2");
- fail("Should have thrown IllegalArgumentException for 'let'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("let"));
- }
-
- // function definitions should be disallowed
- try {
- ExpressionValidator.validateExpression("fn add(a, b) { a + b }");
- fail("Should have thrown IllegalArgumentException for 'fn'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("fn"));
- }
-
- // return statements should be disallowed
- try {
- ExpressionValidator.validateExpression("return r_value * 2");
- fail("Should have thrown IllegalArgumentException for 'return'");
- } catch (IllegalArgumentException e) {
- assertTrue(e.getMessage().contains("return"));
- }
- }
-
- @Test
- public void testNullAndEmptyExpressions() {
- // Null and empty expressions should be allowed (no validation needed)
- ExpressionValidator.validateExpression(null);
- ExpressionValidator.validateExpression("");
- ExpressionValidator.validateExpression(" ");
- }
-}
diff --git a/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
b/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
index add09e5..47372fb 100644
--- a/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
@@ -17,13 +17,10 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
import org.casbin.jcasbin.util.function.CustomFunction;
+import org.casbin.jcasbin.util.function.FunctionArgs;
import org.testng.annotations.Test;
-import java.util.Map;
import java.util.regex.Pattern;
import static org.casbin.jcasbin.main.TestUtil.testEnforce;
@@ -45,10 +42,9 @@ public class FunctionTest {
public static class CustomFunc extends CustomFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1)
{
- String obj = FunctionUtils.getStringValue(arg1, env);
- boolean res = Pattern.compile("/*/url1/url2/*",
Pattern.CASE_INSENSITIVE).matcher(obj).find();
- return AviatorBoolean.valueOf(res);
+ public Object call(Object... args) {
+ String obj = FunctionArgs.getString(args, 0);
+ return Pattern.compile("/*/url1/url2/*",
Pattern.CASE_INSENSITIVE).matcher(obj).find();
}
@Override
diff --git a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
index bf7164c..6428c49 100644
--- a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
@@ -17,8 +17,6 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
import org.casbin.jcasbin.util.Util;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -281,27 +279,6 @@ public class ManagementAPIUnitTest {
testGetRoles(e, "eve", asList("data3_admin"));
}
- @Test
- public void
should_throwsNullPointException_when_setAviatorEvaluator_given_nullInstance() {
- // given
- AviatorEvaluatorInstance instance = null;
- Enforcer enforcer = new Enforcer();
- // when
- Assert.assertThrows(NullPointerException.class,
- () -> enforcer.setAviatorEvaluator(instance));
- }
-
- @Test
- public void should_true_when_setAviatorEvaluator_given_customInstance() {
- // given
- AviatorEvaluatorInstance instance = AviatorEvaluator.newInstance();
- Enforcer enforcer = new Enforcer();
- // when
- enforcer.setAviatorEvaluator(instance);
- // then
- assertEquals(enforcer.getAviatorEval(), instance);
- }
-
@Test
public void testGetAllUsers() {
// Basic RBAC: alice and bob are users, data2_admin is a role
diff --git a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
index 387a3b1..b8b7188 100644
--- a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
@@ -17,15 +17,13 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
import org.casbin.jcasbin.model.Model;
import org.casbin.jcasbin.persist.file_adapter.AdapterMock;
import org.casbin.jcasbin.rbac.RoleManager;
import org.casbin.jcasbin.util.BuiltInFunctions;
import org.casbin.jcasbin.util.Util;
import org.casbin.jcasbin.util.function.CustomFunction;
+import org.casbin.jcasbin.util.function.FunctionArgs;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -869,11 +867,11 @@ public class ModelUnitTest {
public class customFunctionWrapper extends CustomFunction {
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg1,
AviatorObject arg2) {
- String key1 = FunctionUtils.getStringValue(arg1, env);
- String key2 = FunctionUtils.getStringValue(arg2, env);
+ public Object call(Object... args) {
+ String key1 = FunctionArgs.getString(args, 0);
+ String key2 = FunctionArgs.getString(args, 1);
- return AviatorBoolean.valueOf(customFunction(key1, key2));
+ return customFunction(key1, key2);
}
@Override
diff --git a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
index 05dc00c..02af6c2 100644
--- a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
@@ -17,8 +17,6 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
import org.casbin.jcasbin.model.Model;
import org.casbin.jcasbin.persist.Adapter;
import org.casbin.jcasbin.persist.file_adapter.FileAdapter;
@@ -55,7 +53,7 @@ public class SyncedEnforcerUnitTest {
volatile boolean block;
@Override
- public AviatorObject call(Map<String, Object> env, AviatorObject arg) {
+ public Object call(Object... args) {
if (block) {
entered.countDown();
try {
@@ -64,7 +62,7 @@ public class SyncedEnforcerUnitTest {
Thread.currentThread().interrupt();
}
}
- return AviatorBoolean.TRUE;
+ return true;
}
@Override
diff --git a/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java
b/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java
deleted file mode 100644
index d39c5b1..0000000
--- a/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-import org.casbin.jcasbin.util.ExpressionValidator;
-
-public class TestAviatorValidation {
- public static void main(String[] args) {
- System.out.println("Testing Expression Validator:\n");
-
- // Test 1: Valid Casbin expression
- try {
- ExpressionValidator.validateExpression("r.age > 18 && r.role ==
'admin'");
- System.out.println("✅ PASS: Standard Casbin expression allowed");
- } catch (Exception e) {
- System.out.println("❌ FAIL: " + e.getMessage());
- }
-
- // Test 2: Invalid - seq.list()
- try {
- ExpressionValidator.validateExpression("seq.list('A', 'B', 'C')");
- System.out.println("❌ FAIL: seq.list() should be blocked");
- } catch (IllegalArgumentException e) {
- System.out.println("✅ PASS: seq.list() blocked - " +
e.getMessage());
- }
-
- // Test 3: Invalid - string.startsWith()
- try {
- ExpressionValidator.validateExpression("string.startsWith(r.path,
'/admin')");
- System.out.println("❌ FAIL: string.startsWith() should be
blocked");
- } catch (IllegalArgumentException e) {
- System.out.println("✅ PASS: string.startsWith() blocked - " +
e.getMessage());
- }
-
- // Test 4: Valid - custom function
- try {
- ExpressionValidator.validateExpression("myCustomFunc(r.value)");
- System.out.println("✅ PASS: Custom functions allowed");
- } catch (Exception e) {
- System.out.println("❌ FAIL: " + e.getMessage());
- }
-
- // Test 5: Invalid - lambda
- try {
- ExpressionValidator.validateExpression("lambda(x) -> x * 2");
- System.out.println("❌ FAIL: lambda should be blocked");
- } catch (IllegalArgumentException e) {
- System.out.println("✅ PASS: lambda blocked - " + e.getMessage());
- }
- }
-}
diff --git a/src/test/java/org/casbin/jcasbin/main/TestUtil.java
b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
index 2e1155d..f1d2516 100644
--- a/src/test/java/org/casbin/jcasbin/main/TestUtil.java
+++ b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
@@ -17,8 +17,8 @@
package org.casbin.jcasbin.main;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
import org.casbin.jcasbin.rbac.RoleManager;
+import org.casbin.jcasbin.expression.ExpressionFunction;
import org.casbin.jcasbin.util.BuiltInFunctions;
import org.casbin.jcasbin.util.EnforceContext;
import org.casbin.jcasbin.util.SyncedLRUCache;
@@ -280,8 +280,8 @@ public class TestUtil {
assertEquals(res, BuiltInFunctions.keyGet3Func(key1, key2, pathVar));
}
- static void testEval(String eval, Map<String, Object> env,
AviatorEvaluatorInstance aviatorEval, boolean res) {
- assertEquals(res, BuiltInFunctions.eval(eval, env, aviatorEval));
+ static void testEval(String eval, Map<String, Object> env, Map<String,
ExpressionFunction> functions, boolean res) {
+ assertEquals(res, BuiltInFunctions.eval(eval, env, functions));
}
static void testKeyMatch(String key1, String key2, boolean res) {
diff --git a/src/test/java/org/casbin/jcasbin/main/UtilTest.java
b/src/test/java/org/casbin/jcasbin/main/UtilTest.java
index f2f159d..91f0b17 100644
--- a/src/test/java/org/casbin/jcasbin/main/UtilTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/UtilTest.java
@@ -59,32 +59,6 @@ public class UtilTest {
assertEquals("r_obj == \"a\\\"p.b\" || p_obj == 'r.c'",
Util.escapeAssertion("r.obj == \"a\\\"p.b\" || p.obj == 'r.c'"));
}
- @Test
- public void testConvertInSyntax(){
- // Basic cases
- assertEquals("include(r_obj, r_sub)", Util.convertInSyntax("r_sub in
r_obj"));
- assertEquals("include(r_obj, r_sub.name)",
Util.convertInSyntax("r_sub.name in r_obj"));
- assertEquals("include(r_obj.name, r_sub.name)",
Util.convertInSyntax("r_sub.name in r_obj.name"));
- assertEquals("include(r_obj, r_sub) && r.obj == p.obj",
Util.convertInSyntax("r_sub in r_obj && r.obj == p.obj"));
-
- // Tuple/array with single quotes - should convert to tuple() function
- assertEquals("include(tuple('data2', 'data3'), r.obj)",
Util.convertInSyntax("r.obj in ('data2', 'data3')"));
-
- // Tuple/array with double quotes - should convert to tuple() function
- assertEquals("include(tuple(\"data2\", \"data3\"), r.obj)",
Util.convertInSyntax("r.obj in (\"data2\", \"data3\")"));
-
- // Tuple/array without quotes - should convert to tuple() function
- assertEquals("include(tuple(data2, data3), r.obj)",
Util.convertInSyntax("r.obj in (data2, data3)"));
-
- // Complex expression with 'in' operator and tuple (the original issue
scenario)
- assertEquals("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act ||
include(tuple('data2', 'data3'), r.obj)",
- Util.convertInSyntax("g(r.sub, p.sub) && r.obj == p.obj
&& r.act == p.act || r.obj in ('data2', 'data3')"));
-
- // Multiple 'in' operators
- assertEquals("include(r.obj.admins, r.sub.name) &&
include(r.obj.admins, (\"bob\"))",
- Util.convertInSyntax("r.sub.name in r.obj.admins &&
(\"bob\") in r.obj.admins"));
- }
-
@Test
public void testRemoveComments(){
assertEquals("r.act == p.act", Util.removeComments("r.act == p.act #
comments"));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]