This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/accumulo-access.git
The following commit(s) were added to refs/heads/main by this push: new 2f68281 memoizes creating a parse tree (#84) 2f68281 is described below commit 2f6828151bffada062ec82faacfa6164a1f4f473 Author: Keith Turner <ktur...@apache.org> AuthorDate: Tue Feb 11 18:32:22 2025 -0500 memoizes creating a parse tree (#84) This change offers a new parse() method on AccessExpression that creates a parse tree if one does not exits. This change allows decoupling how access expression are created (w/ or w/o an initial parse tree) from using parse trees. For example code the like the following that wanted to use a parse tree to validate an access expression would have required the ParsedAccessExpression type to be passed to it. Now it can accept an AccessExpression and call parse() which will do the most efficient thing depending on how the expression was created. ```java void checkExpression(AccessExpression expression){ var parsed = expression.parse(); // use parse tree to validate expression } ``` --- .../org/apache/accumulo/access/AccessExpression.java | 13 ++++++++++++- .../apache/accumulo/access/AccessExpressionImpl.java | 13 +++++++++++++ .../accumulo/access/ParsedAccessExpressionImpl.java | 5 +++++ .../accumulo/access/ParsedAccessExpressionTest.java | 18 +++++++++++++++++- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/accumulo/access/AccessExpression.java b/src/main/java/org/apache/accumulo/access/AccessExpression.java index 2bdc90e..6f0f18b 100644 --- a/src/main/java/org/apache/accumulo/access/AccessExpression.java +++ b/src/main/java/org/apache/accumulo/access/AccessExpression.java @@ -107,6 +107,15 @@ public abstract class AccessExpression implements Serializable { */ public abstract String getExpression(); + /** + * Parses the access expression if it was never parsed before. If this access expression was + * created using {@link #parse(String)} or {@link #parse(byte[])} then it will have a parse from + * inception and this method will return itself. If the access expression was created using + * {@link #of(String)} or {@link #of(byte[])} then this method will create a parse tree the first + * time its called and remember it, returning the remembered parse tree on subsequent calls. + */ + public abstract ParsedAccessExpression parse(); + @Override public boolean equals(Object o) { if (o instanceof AccessExpression) { @@ -170,7 +179,9 @@ public abstract class AccessExpression implements Serializable { /** * Validates an access expression and returns an immutable object with a parse tree. Creating the * parse tree is expensive relative to calling {@link #of(String)} or {@link #validate(String)}, - * so only use this method when the parse tree is needed. + * so only use this method when the parse tree is always needed. If the code may only use the + * parse tree sometimes, then it may be best to call {@link #of(String)} to create the access + * expression and then call {@link AccessExpression#parse()} when needed. * * @throws NullPointerException when the argument is null * @throws InvalidAccessExpressionException if the given expression is not valid diff --git a/src/main/java/org/apache/accumulo/access/AccessExpressionImpl.java b/src/main/java/org/apache/accumulo/access/AccessExpressionImpl.java index 7a031b8..5dbddfd 100644 --- a/src/main/java/org/apache/accumulo/access/AccessExpressionImpl.java +++ b/src/main/java/org/apache/accumulo/access/AccessExpressionImpl.java @@ -27,6 +27,7 @@ final class AccessExpressionImpl extends AccessExpression { public static final AccessExpression EMPTY = new AccessExpressionImpl(""); private final String expression; + private volatile ParsedAccessExpression parsed = null; AccessExpressionImpl(String expression) { validate(expression); @@ -42,4 +43,16 @@ final class AccessExpressionImpl extends AccessExpression { public String getExpression() { return expression; } + + @Override + public ParsedAccessExpression parse() { + if (parsed == null) { + synchronized (this) { + if (parsed == null) { + parsed = ParsedAccessExpressionImpl.parseExpression(expression.getBytes(UTF_8)); + } + } + } + return parsed; + } } diff --git a/src/main/java/org/apache/accumulo/access/ParsedAccessExpressionImpl.java b/src/main/java/org/apache/accumulo/access/ParsedAccessExpressionImpl.java index b6e316b..efb8cfc 100644 --- a/src/main/java/org/apache/accumulo/access/ParsedAccessExpressionImpl.java +++ b/src/main/java/org/apache/accumulo/access/ParsedAccessExpressionImpl.java @@ -93,6 +93,11 @@ final class ParsedAccessExpressionImpl extends ParsedAccessExpression { return stringExpression.get(); } + @Override + public ParsedAccessExpression parse() { + return this; + } + @Override public ExpressionType getType() { return type; diff --git a/src/test/java/org/apache/accumulo/access/ParsedAccessExpressionTest.java b/src/test/java/org/apache/accumulo/access/ParsedAccessExpressionTest.java index 27eae1a..a5cc065 100644 --- a/src/test/java/org/apache/accumulo/access/ParsedAccessExpressionTest.java +++ b/src/test/java/org/apache/accumulo/access/ParsedAccessExpressionTest.java @@ -24,6 +24,8 @@ import static org.apache.accumulo.access.ParsedAccessExpression.ExpressionType.A import static org.apache.accumulo.access.ParsedAccessExpression.ExpressionType.EMPTY; import static org.apache.accumulo.access.ParsedAccessExpression.ExpressionType.OR; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; @@ -35,7 +37,8 @@ public class ParsedAccessExpressionTest { public void testParsing() { String expression = "(BLUE&(RED|PINK|YELLOW))|((YELLOW|\"GREEN/GREY\")&(RED|BLUE))|BLACK"; for (var parsed : List.of(AccessExpression.parse(expression), - AccessExpression.parse(expression.getBytes(UTF_8)))) { + AccessExpression.parse(expression.getBytes(UTF_8)), AccessExpression.of(expression).parse(), + AccessExpression.of(expression.getBytes(UTF_8)).parse())) { // verify root node verify("(BLUE&(RED|PINK|YELLOW))|((YELLOW|\"GREEN/GREY\")&(RED|BLUE))|BLACK", OR, 3, parsed); @@ -69,6 +72,18 @@ public class ParsedAccessExpressionTest { verify("", EMPTY, 0, parsed); } + @Test + public void testParseTwice() { + for (var expression : List.of(AccessExpression.of("A&B"), + AccessExpression.of("A&B".getBytes(UTF_8)))) { + var parsed = expression.parse(); + assertNotSame(expression, parsed); + assertEquals(expression.getExpression(), parsed.getExpression()); + assertSame(parsed, expression.parse()); + assertSame(parsed, expression.parse()); + } + } + /** * Traverses a path in the parse tree an verifies the node at the end of the path. */ @@ -81,6 +96,7 @@ public class ParsedAccessExpressionTest { assertEquals(expectedExpression, parsed.getExpression()); assertEquals(expectedType, parsed.getType()); assertEquals(expectedChildren, parsed.getChildren().size()); + assertSame(parsed, parsed.parse()); // check list of children is immutable var fp = parsed; assertThrows(UnsupportedOperationException.class, () -> fp.getChildren().clear());