This is an automated email from the ASF dual-hosted git repository.

dlmarion 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 8ef5fb5  Add ANTLR grammar and tests to contrib directory
8ef5fb5 is described below

commit 8ef5fb5246edd70ae7ff9c95893f62c0f83478d6
Author: Dave Marion <dlmar...@apache.org>
AuthorDate: Fri Sep 22 16:11:54 2023 -0400

    Add ANTLR grammar and tests to contrib directory
    
    Added contrib/antlr4 directory which contains an ANTLRv4 grammar
    file for parsing AccessExpressions. I also pulled in the ANTLRv4 ABNF
    grammar file[1] and used that to parse the ABNF specification that we
    created to validate that the specification is syntactically correct. I 
created
    tests to validate that the AccessExpression grammar is correct. The
    ANTLRv4 grammar file (AccessExpression.g4) can be used by other
    projects that are written in a language supported by ANTLR4 (c++, c#,
    python, etc.) to validate that the access expressions that they are
    creating are valid.
    
    [1] https://github.com/antlr/grammars-v4/blob/master/abnf/Abnf.g4
---
 README.md                                          |  17 +-
 SPECIFICATION.md                                   |  25 ++-
 contrib/.gitignore                                 |  33 ++++
 contrib/antlr4/README.md                           |  23 +++
 pom.xml => contrib/antlr4/pom.xml                  |  77 ++++----
 contrib/antlr4/src/main/antlr4/Abnf.g4             | 163 +++++++++++++++++
 contrib/antlr4/src/main/antlr4/AccessExpression.g4 |  36 ++++
 .../org/apache/accumulo/access/TestDataLoader.java |  77 ++++++++
 .../accumulo/access/grammar/SpecificationTest.java |  66 +++++++
 .../antlr/AccessExpressionAntlrBenchmark.java      | 165 +++++++++++++++++
 .../antlr/AccessExpressionAntlrEvaluator.java      | 117 ++++++++++++
 .../grammar/antlr/AccessExpressionAntlrParser.java |  96 ++++++++++
 .../accumulo/access/grammar/antlr/Antlr4Tests.java | 202 +++++++++++++++++++++
 .../antlr4/src/test/resources/specification.abnf   |  17 ++
 pom.xml                                            |   1 +
 .../apache/accumulo/access/AccessEvaluator.java    |   1 -
 .../accumulo/access/AccessEvaluatorTest.java       |   1 -
 src/test/resources/testdata.json                   |   4 +-
 18 files changed, 1056 insertions(+), 65 deletions(-)

diff --git a/README.md b/README.md
index 30f313d..2b86199 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,17 @@
 
 -->
 
-# accumulo-access
-Accumulo Access Control Library
+# Accumulo Access Control Library
 
-This project is a work in progress with the following goals.
+This library is a stand-alone java library that provides the functionality of 
the Apache Accumulo ColumnVisibility for use outside of Accumulo.
+
+## Concepts
+
+ * AccessExpression - a boolean expression of attributes required to access an 
object (e.g. Key/Value pair in Accumulo). See SPECIFICATION.md.
+ * Authorizations - a set of attributes, typically attributed to the entity 
trying to access an object.
+ * AccessEvaluator - An object that determines if the entity can access the 
object using the entity's Authorizations and the objects AccessExpression.
+
+## Goals
 
  * Create a standalone java library that offers the Accumulo visibility 
functionality
  * Support the same syntax and semantics as ColumnVisibility and 
VisibilityEvaluator initially.  This will allow ColumnVisibility and 
VisibilityEvaluator to adapt to use this new library for their implementation.
@@ -30,7 +37,9 @@ This project is a work in progress with the following goals.
  * Use no external types (like Hadoop types) in its API.
  * Use semantic versioning.
 
-The following types constitute the public API of this library.  All other 
types are package private and are not part of the public API.
+## Public API
+
+The following types constitute the public API of this library. All other types 
are package private and are not part of the public API.
 
   * 
[IllegalAccessExpressionException](src/main/java/org/apache/accumulo/access/IllegalAccessExpressionException.java).
   * 
[AccessEvaluator](src/main/java/org/apache/accumulo/access/AccessEvaluator.java).
diff --git a/SPECIFICATION.md b/SPECIFICATION.md
index e733c13..51d99db 100644
--- a/SPECIFICATION.md
+++ b/SPECIFICATION.md
@@ -22,7 +22,7 @@
 # AccessExpression Specification
 
 This document specifies the format of an Apache Accumulo AccessExpression. An 
AccessExpression
-is an encoding of a boolean expression of the attributes that a subject is 
required to have to
+is an encoding of a boolean expression of the attributes that an entity is 
required to have to
 access a particular piece of data.
 
 ## Syntax
@@ -44,13 +44,12 @@ or-expression           =  "|" (access-token / 
paren-expression) [or-expression]
 access-token            = 1*( ALPHA / DIGIT / "_" / "-" / "." / ":" / slash )
 access-token            =/ DQUOTE 1*(utf8-subset / escaped) DQUOTE
 
-utf8-subset             = %x20-21 / %x23-5B / %5D-7E / UVCHARBEYONDASCII ; 
utf8 minus '"' and '\'
+utf8-subset             = %x20-21 / %x23-5B / %x5D-7E / unicode-beyond-ascii ; 
utf8 minus '"' and '\'
+unicode-beyond-ascii    = %x0080-D7FF / %xE000-10FFFF
 escaped                 = "\" DQUOTE / "\\"
 slash                   = "/"
 ```
 
-The definition of utf8 was borrowed from this [ietf document][2]. TODO that 
doc defines unicode and not utf8
-
 ## Serialization
 
 An AccessExpression is a UTF-8 string. It can be serialized using a byte array 
as long as it
@@ -58,13 +57,13 @@ can be deserialized back into the same UTF-8 string.
 
 ## Evaluation
 
-Evaluation of access expressions performs a combination of [set][3] existence
-checks and [boolean algebra][4]. Access expression use the following from
+Evaluation of access expressions performs a combination of [set][2] existence
+checks and [boolean algebra][3]. Access expression use the following from
 boolean algebra.
 
- * The symbol `&` in an access expression represents [logical conjunction][5]
+ * The symbol `&` in an access expression represents [logical conjunction][4]
    which is represented in a boolean algebra as `∧`.
- * The symbol `|` in an access expression represents [logical disjunction][6]
+ * The symbol `|` in an access expression represents [logical disjunction][5]
    which is represented in a boolean algebra as `∨`.
 
 When evaluating an access expression set existence checks are done against a
@@ -119,9 +118,7 @@ Notice above when checking if `"abc\\xyz"` exist in the set 
that it is unquoted
 and the `\` character is unescaped.
 
 [1]: https://www.rfc-editor.org/rfc/rfc5234
-[2]: 
https://datatracker.ietf.org/doc/html/draft-seantek-unicode-in-abnf-03#section-4.2
-[3]: https://en.wikipedia.org/wiki/Set_(mathematics)
-[4]: https://en.wikipedia.org/wiki/Boolean_algebra
-[5]: https://en.wikipedia.org/wiki/Logical_conjunction
-[6]: https://en.wikipedia.org/wiki/Logical_disjunction
-
+[2]: https://en.wikipedia.org/wiki/Set_(mathematics)
+[3]: https://en.wikipedia.org/wiki/Boolean_algebra
+[4]: https://en.wikipedia.org/wiki/Logical_conjunction
+[5]: https://en.wikipedia.org/wiki/Logical_disjunction
diff --git a/contrib/.gitignore b/contrib/.gitignore
new file mode 100644
index 0000000..7cd1fdc
--- /dev/null
+++ b/contrib/.gitignore
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+/antlr4/target/
+
+/antlr4/.settings/
+/antlr4/.project
+/antlr4/.classpath
+/antlr4/.pydevproject
+/antlr4/.idea
+/antlr4/*.iml
+/antlr4/*.ipr
+/antlr4/*.iws
+/antlr4/nbproject/
+/antlr4/nbactions.xml
+/antlr4/nb-configuration.xml
+/antlr4/.vscode/
+/antlr4/.factorypath
diff --git a/contrib/antlr4/README.md b/contrib/antlr4/README.md
new file mode 100644
index 0000000..1b90603
--- /dev/null
+++ b/contrib/antlr4/README.md
@@ -0,0 +1,23 @@
+<!--
+    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.
+-->
+# ANTLR Example
+
+This contrib example contains an [ANTLRv4](https://www.antlr.org/) grammar 
file (see [AccessExpression.g4](src/main/antlr4/AccessExpression.g4)) that can 
be used to create AccessExpression parsers in languages supported by ANTLRv4. 
For example, a project could use this to validate that AccessExpression's are 
valid before sending them to Accumulo in the ColumnVisibility field of the Key.
+
+An example 
[parser](src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java)
 and 
[evaluator](src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java)
 are used when building this project to confirm that the parsing and evaluation 
are consistent with the reference Java implementation.
+
+ANTLR was evaluated as a replacement for the existing custom Java parser, but 
it doesn't parse as fast as the custom implementation. You can view the 
performance differences by running the JMH benchmark in this contrib project 
and the one in the main project.
diff --git a/pom.xml b/contrib/antlr4/pom.xml
similarity index 72%
copy from pom.xml
copy to contrib/antlr4/pom.xml
index e2147ce..ca30fbf 100644
--- a/pom.xml
+++ b/contrib/antlr4/pom.xml
@@ -1,24 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    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.
-
--->
 <project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
   <modelVersion>4.0.0</modelVersion>
   <parent>
@@ -27,16 +7,28 @@
     <version>30</version>
   </parent>
   <groupId>org.apache.accumulo</groupId>
-  <artifactId>accumulo-access</artifactId>
+  <artifactId>accumulo-access-antlr4</artifactId>
   <version>1.0.0-SNAPSHOT</version>
-  <name>Apache Accumulo Access</name>
+  <name>Apache Accumulo Access Antlr4 Example</name>
   <properties>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
+    
<maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <version.jmh>1.36</version.jmh>
   </properties>
   <dependencies>
+    <dependency>
+      <groupId>org.antlr</groupId>
+      <artifactId>antlr4-runtime</artifactId>
+      <version>4.13.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.accumulo</groupId>
+      <artifactId>accumulo-access</artifactId>
+      <version>1.0.0-SNAPSHOT</version>
+    </dependency>
     <dependency>
       <groupId>com.github.spotbugs</groupId>
       <artifactId>spotbugs-annotations</artifactId>
@@ -96,6 +88,24 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.antlr</groupId>
+        <artifactId>antlr4-maven-plugin</artifactId>
+        <version>4.13.1</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>antlr4</goal>
+            </goals>
+            <phase>generate-sources</phase>
+            <configuration>
+              
<outputDirectory>target/generated-sources/antlr4/org/apache/accumulo/access/grammars</outputDirectory>
+              <listener>false</listener>
+              <visitor>false</visitor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
       <plugin>
         <groupId>net.revelc.code</groupId>
         <artifactId>impsort-maven-plugin</artifactId>
@@ -118,7 +128,7 @@
         <artifactId>formatter-maven-plugin</artifactId>
         <version>2.23.0</version>
         <configuration>
-          <configFile>src/build/eclipse-codestyle.xml</configFile>
+          <configFile>../../src/build/eclipse-codestyle.xml</configFile>
           <lineEnding>LF</lineEnding>
           <skipCssFormatting>true</skipCssFormatting>
           <skipHtmlFormatting>true</skipHtmlFormatting>
@@ -135,27 +145,6 @@
           </execution>
         </executions>
       </plugin>
-      <plugin>
-        <groupId>net.revelc.code</groupId>
-        <artifactId>apilyzer-maven-plugin</artifactId>
-        <version>1.3.0</version>
-        <executions>
-          <execution>
-            <id>apilyzer</id>
-            <goals>
-              <goal>analyze</goal>
-            </goals>
-            <configuration>
-              <includes>
-                
<include>org[.]apache[.]accumulo[.]access[.]IllegalAccessExpressionException</include>
-                
<include>org[.]apache[.]accumulo[.]access[.]AccessExpression</include>
-                
<include>org[.]apache[.]accumulo[.]access[.]AccessEvaluator</include>
-                
<include>org[.]apache[.]accumulo[.]access[.]Authorizations</include>
-              </includes>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
     </plugins>
   </build>
 </project>
diff --git a/contrib/antlr4/src/main/antlr4/Abnf.g4 
b/contrib/antlr4/src/main/antlr4/Abnf.g4
new file mode 100644
index 0000000..0eb9088
--- /dev/null
+++ b/contrib/antlr4/src/main/antlr4/Abnf.g4
@@ -0,0 +1,163 @@
+/*
+BSD License
+
+Copyright (c) 2013, Rainer Schuster
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of Rainer Schuster nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ABNF grammar derived from:
+
+    http://tools.ietf.org/html/rfc5234
+
+    Augmented BNF for Syntax Specifications: ABNF
+    January 2008
+
+    http://tools.ietf.org/html/rfc7405
+
+    Case-Sensitive String Support in ABNF
+    December 2014
+
+Terminal rules mainly created by ANTLRWorks 1.5 sample code.
+ */
+
+/*
+ * This is a copy of 
https://github.com/antlr/grammars-v4/blob/master/abnf/Abnf.g4 that
+ * has been modified to specify the package name for the generated java code
+ */
+grammar Abnf;
+
+ @header {
+   package org.apache.accumulo.access.grammars;
+ }
+
+
+// Note: Whitespace handling not as strict as in the specification.
+
+rulelist
+   : rule_* EOF
+   ;
+
+rule_
+   : ID '=' '/'? elements
+   ;
+
+elements
+   : alternation
+   ;
+
+alternation
+   : concatenation ( '/' concatenation )*
+   ;
+
+concatenation
+   : repetition +
+   ;
+
+repetition
+   : repeat_? element
+   ;
+
+repeat_
+   : INT | INT? '*' INT?
+   ;
+
+element
+   : ID | group | option | STRING | NumberValue | ProseValue
+   ;
+
+group
+   : '(' alternation ')'
+   ;
+
+option
+   : '[' alternation ']'
+   ;
+
+
+NumberValue
+   : '%' ( BinaryValue | DecimalValue | HexValue )
+   ;
+
+
+fragment BinaryValue
+   : 'b' BIT+ ( ( '.' BIT+ )+ | '-' BIT+ )?
+   ;
+
+
+fragment DecimalValue
+   : 'd' DIGIT+ ( ( '.' DIGIT+ )+ | '-' DIGIT+ )?
+   ;
+
+
+fragment HexValue
+   : 'x' HEX_DIGIT+ ( ( '.' HEX_DIGIT+ )+ | '-' HEX_DIGIT+ )?
+   ;
+
+
+ProseValue
+   : '<' ~'>'* '>'
+   ;
+
+ID
+   : LETTER ( LETTER | DIGIT | '-' )*
+   ;
+
+INT
+   : '0' .. '9'+
+   ;
+
+COMMENT
+   : ';' ~ ( '\n' | '\r' )* '\r'? '\n' -> channel ( HIDDEN )
+   ;
+
+WS
+   : ( ' ' | '\t' | '\r' | '\n' ) -> channel ( HIDDEN )
+   ;
+
+
+STRING
+   : ( '%s' | '%i' )? '"' ~'"'* '"'
+   ;
+
+fragment LETTER : 'a' .. 'z' | 'A' .. 'Z';
+
+fragment BIT
+   : '0' .. '1'
+   ;
+
+
+fragment DIGIT
+   : '0' .. '9'
+   ;
+
+
+// Note: from the RFC errata 
(http://www.rfc-editor.org/errata_search.php?rfc=5234&eid=4040):
+// > ABNF strings are case insensitive and the character set for these strings 
is US-ASCII.
+// > So the definition of HEXDIG already allows for both upper and lower case 
(or a mixture).
+fragment HEX_DIGIT
+   : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F'
+   ;
diff --git a/contrib/antlr4/src/main/antlr4/AccessExpression.g4 
b/contrib/antlr4/src/main/antlr4/AccessExpression.g4
new file mode 100644
index 0000000..e945c6d
--- /dev/null
+++ b/contrib/antlr4/src/main/antlr4/AccessExpression.g4
@@ -0,0 +1,36 @@
+/*
+ * 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.
+*/
+grammar AccessExpression;
+
+@header {
+  package org.apache.accumulo.access.grammars;
+}
+
+access_expression : EOF | expression EOF;
+expression :     ( and_expression | or_expression | '(' expression ')' | 
access_token);
+and_expression : ( access_token | '(' expression ')' ) (and_operator ( 
access_token | '(' expression ')' ) )+;
+or_expression :  ( access_token | '(' expression ')' ) (or_operator ( 
access_token | '(' expression ')' ) )+;
+access_token : ACCESS_TOKEN;
+and_operator : '&';
+or_operator : '|';
+
+
+ACCESS_TOKEN : ( [A-Za-z] | [0-9] | '_' | '-' | '.' | ':' | '/' )+
+               | '"' ( [\u0020-\u0021] | [\u0023-\u005B] | [\u005D-\u007E] | 
[\u0080-\uD7FF] | [\uE000-\u{10FFFF}] | ( '\\"' | '\\\\' ) )+ '"' ;
+WS : [\r\t\b\u000C]+ -> skip;
\ No newline at end of file
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java
new file mode 100644
index 0000000..bb858d0
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java
@@ -0,0 +1,77 @@
+/*
+ * 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.apache.accumulo.access;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+public class TestDataLoader {
+
+  public enum ExpectedResult {
+    ACCESSIBLE, INACCESSIBLE, ERROR
+  }
+
+  public static class TestExpressions {
+    public ExpectedResult expectedResult;
+    public String[] expressions;
+  }
+
+  public static class TestDataSet {
+    public String description;
+
+    public String[][] auths;
+
+    public List<TestExpressions> tests;
+  }
+
+  public static List<TestDataSet> readTestData() throws IOException, 
URISyntaxException {
+
+    URL url = TestDataLoader.class.getClassLoader().getResource(".");
+    File testClassesDir = new File(url.toURI());
+    File accumuloAccessParentDir =
+        
testClassesDir.getParentFile().getParentFile().getParentFile().getParentFile();
+    File accumuloAccessSourceDir = new File(accumuloAccessParentDir, "src");
+    assertTrue(accumuloAccessSourceDir.exists());
+    File accumuloAccessTestDir = new File(accumuloAccessSourceDir, "test");
+    assertTrue(accumuloAccessTestDir.exists());
+    File accumuloAccessTestResourcesDir = new File(accumuloAccessTestDir, 
"resources");
+    assertTrue(accumuloAccessTestResourcesDir.exists());
+    File testDataFile = new File(accumuloAccessTestResourcesDir, 
"testdata.json");
+    assertTrue(testDataFile.exists());
+
+    try (FileInputStream input = new FileInputStream(testDataFile)) {
+      var json = new String(input.readAllBytes(), UTF_8);
+
+      Type listType = new TypeToken<ArrayList<TestDataSet>>() {}.getType();
+      return new Gson().fromJson(json, listType);
+    }
+  }
+}
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java
 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java
new file mode 100644
index 0000000..d0362db
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java
@@ -0,0 +1,66 @@
+package org.apache.accumulo.access.grammar;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ConsoleErrorListener;
+import org.antlr.v4.runtime.LexerNoViableAltException;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.apache.accumulo.access.grammar.antlr.Antlr4Tests;
+import org.apache.accumulo.access.grammars.AbnfLexer;
+import org.apache.accumulo.access.grammars.AbnfParser;
+import org.junit.jupiter.api.Test;
+
+// This test uses the ANTLR ABNF grammar to parse the
+// accumulo-access ANBF specification to validate that
+// it is proper ANBF.
+public class SpecificationTest {
+
+  @Test
+  public void testAbnfSpecificationParses() throws Exception {
+
+    // The test resource specification.abnf is a copy of the ABNF
+    // from SPECIFICATION.md
+
+    InputStream is = 
Antlr4Tests.class.getResourceAsStream("/specification.abnf");
+    assertNotNull(is);
+
+    final AtomicLong errors = new AtomicLong(0);
+
+    AbnfLexer lexer = new AbnfLexer(CharStreams.fromStream(is)) {
+      @Override
+      public void recover(LexerNoViableAltException e) {
+        super.recover(e);
+        errors.incrementAndGet();
+      }
+
+      @Override
+      public void recover(RecognitionException re) {
+        super.recover(re);
+        errors.incrementAndGet();
+      }
+    };
+
+    AbnfParser parser = new AbnfParser(new CommonTokenStream(lexer));
+    parser.removeErrorListeners();
+    parser.addErrorListener(new ConsoleErrorListener() {
+      @Override
+      public void syntaxError(Recognizer<?,?> recognizer, Object 
offendingSymbol, int line,
+          int charPositionInLine, String msg, RecognitionException e) {
+        super.syntaxError(recognizer, offendingSymbol, line, 
charPositionInLine, msg, e);
+        errors.incrementAndGet();
+      }
+    });
+
+    parser.rulelist();
+    assertEquals(0, errors.get());
+
+  }
+
+}
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java
 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java
new file mode 100644
index 0000000..80ad3a3
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java
@@ -0,0 +1,165 @@
+package org.apache.accumulo.access.grammar.antlr;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.accumulo.access.Authorizations;
+import org.apache.accumulo.access.TestDataLoader;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+/**
+ * Benchmarks Access Expressions using JMH. To run, use the following commands.
+ *
+ * <p>
+ * <blockquote>
+ *
+ * <pre>
+ * mvn clean package
+ * mvn exec:exec -Dexec.executable="java" -Dexec.classpathScope=test 
-Dexec.args="-classpath %classpath 
org.apache.accumulo.access.grammar.antlr.AccessExpressionAntlrBenchmark"
+ * </code></blockquote>
+ * </pre>
+ */
+public class AccessExpressionAntlrBenchmark {
+
+  public static class EvaluatorTests {
+    AccessExpressionAntlrEvaluator evaluator;
+
+    List<Access_expressionContext> parsedExpressions;
+
+    List<byte[]> expressions;
+  }
+
+  @State(Scope.Benchmark)
+  public static class BenchmarkState {
+
+    private ArrayList<byte[]> allTestExpressions;
+
+    private ArrayList<String> allTestExpressionsStr;
+
+    private ArrayList<EvaluatorTests> evaluatorTests;
+
+    @Setup
+    public void loadData() throws IOException, URISyntaxException {
+      List<TestDataLoader.TestDataSet> testData = 
TestDataLoader.readTestData();
+      allTestExpressions = new ArrayList<>();
+      allTestExpressionsStr = new ArrayList<>();
+      evaluatorTests = new ArrayList<>();
+
+      for (var testDataSet : testData) {
+        EvaluatorTests et = new EvaluatorTests();
+        et.parsedExpressions = new ArrayList<>();
+        et.expressions = new ArrayList<>();
+
+        et.evaluator = new AccessExpressionAntlrEvaluator(
+            
Stream.of(testDataSet.auths).map(Authorizations::of).collect(Collectors.toList()));
+
+        for (var tests : testDataSet.tests) {
+          if (tests.expectedResult != TestDataLoader.ExpectedResult.ERROR) {
+            for (var exp : tests.expressions) {
+              allTestExpressionsStr.add(exp);
+              byte[] byteExp = exp.getBytes(UTF_8);
+              allTestExpressions.add(byteExp);
+              et.expressions.add(byteExp);
+              
et.parsedExpressions.add(AccessExpressionAntlrParser.parseAccessExpression(exp));
+            }
+          }
+        }
+
+        evaluatorTests.add(et);
+      }
+    }
+
+    List<byte[]> getBytesExpressions() {
+      return allTestExpressions;
+    }
+
+    List<String> getStringExpressions() {
+      return allTestExpressionsStr;
+    }
+
+    public ArrayList<EvaluatorTests> getEvaluatorTests() {
+      return evaluatorTests;
+    }
+
+  }
+
+  /**
+   * Measures the time it takes to parse an expression stored in byte[] and 
produce a parse tree.
+   */
+  @Benchmark
+  public void measureBytesParsing(BenchmarkState state, Blackhole blackhole) {
+    for (byte[] accessExpression : state.getBytesExpressions()) {
+      
blackhole.consume(AccessExpressionAntlrParser.parseAccessExpression(accessExpression));
+    }
+  }
+
+  /**
+   * Measures the time it takes to parse an expression stored in a String and 
produce a parse tree.
+   */
+  @Benchmark
+  public void measureStringParsing(BenchmarkState state, Blackhole blackhole) {
+    for (String accessExpression : state.getStringExpressions()) {
+      
blackhole.consume(AccessExpressionAntlrParser.parseAccessExpression(accessExpression));
+    }
+  }
+
+  /**
+   * Measures the time it takes to evaluate a previously parsed expression.
+   */
+  @Benchmark
+  public void measureEvaluation(BenchmarkState state, Blackhole blackhole) {
+    for (EvaluatorTests evaluatorTests : state.getEvaluatorTests()) {
+      for (Access_expressionContext expression : 
evaluatorTests.parsedExpressions) {
+        blackhole.consume(evaluatorTests.evaluator.canAccess(expression));
+      }
+    }
+  }
+
+  /**
+   * Measures the time it takes to parse and evaluate an expression. This has 
to create the parse
+   * tree an operate on it.
+   */
+  @Benchmark
+  public void measureEvaluationAndParsing(BenchmarkState state, Blackhole 
blackhole) {
+    for (EvaluatorTests evaluatorTests : state.getEvaluatorTests()) {
+      for (byte[] expression : evaluatorTests.expressions) {
+        blackhole.consume(evaluatorTests.evaluator.canAccess(expression));
+      }
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+
+    var state = new BenchmarkState();
+    state.loadData();
+
+    int numExpressions = state.getBytesExpressions().size();
+
+    System.out.println("Number of Expressions: " + numExpressions);
+
+    Options opt = new 
OptionsBuilder().include(AccessExpressionAntlrBenchmark.class.getSimpleName())
+        .mode(Mode.Throughput).operationsPerInvocation(numExpressions)
+        
.timeUnit(TimeUnit.MICROSECONDS).warmupTime(TimeValue.seconds(5)).warmupIterations(3)
+        .measurementIterations(4).forks(3).build();
+
+    new Runner(opt).run();
+  }
+
+}
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java
 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java
new file mode 100644
index 0000000..98fe4a8
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java
@@ -0,0 +1,117 @@
+package org.apache.accumulo.access.grammar.antlr;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.accumulo.access.AccessEvaluator;
+import org.apache.accumulo.access.AccessExpression;
+import org.apache.accumulo.access.Authorizations;
+import org.apache.accumulo.access.IllegalAccessExpressionException;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Access_tokenContext;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.And_expressionContext;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.And_operatorContext;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Or_expressionContext;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Or_operatorContext;
+
+public class AccessExpressionAntlrEvaluator implements AccessEvaluator {
+
+  private class Entity {
+
+    private Set<String> authorizations;
+
+    @Override
+    public String toString() {
+      return "Entity [authorizations=" + authorizations + "]";
+    }
+
+  }
+
+  private final List<Entity> entities;
+
+  public AccessExpressionAntlrEvaluator(List<Authorizations> authSets) {
+    entities = new ArrayList<>(authSets.size());
+
+    for (Authorizations a : authSets) {
+      Set<String> entityAuths = a.asSet();
+      Entity e = new Entity();
+      entities.add(e);
+      e.authorizations = new HashSet<>(entityAuths.size() * 2);
+      a.asSet().stream().forEach(auth -> {
+        e.authorizations.add(auth);
+        String quoted = AccessExpression.quote(auth);
+        if (!quoted.startsWith("\"")) {
+          quoted = '"' + quoted + '"';
+        }
+        e.authorizations.add(quoted);
+      });
+    }
+  }
+
+  public boolean canAccess(byte[] accessExpression) throws 
IllegalAccessExpressionException {
+    return canAccess(AccessExpression.of(accessExpression));
+  }
+
+  public boolean canAccess(AccessExpression accessExpression) {
+    return canAccess(accessExpression.getExpression());
+  }
+
+  public boolean canAccess(String accessExpression) {
+    if ("".equals(accessExpression)) {
+      return true;
+    }
+    return 
canAccess(AccessExpressionAntlrParser.parseAccessExpression(accessExpression));
+  }
+
+  public boolean canAccess(Access_expressionContext parsedExpression) {
+    for (Entity e : entities) {
+      if (!evaluate(e, parsedExpression)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private Boolean evaluate(Entity e, ParseTree node) {
+    Boolean retval;
+    if (node instanceof Access_tokenContext) {
+      retval = e.authorizations.contains(node.getText());
+    } else if (node instanceof TerminalNode
+        || node instanceof And_operatorContext | node instanceof 
Or_operatorContext) {
+      retval = null;
+    } else {
+      int childCount = node.getChildCount();
+      int trueCount = 0;
+      int falseCount = 0;
+      for (int i = 0; i < childCount; i++) {
+        Boolean childResult = evaluate(e, node.getChild(i));
+        if (childResult == null) {
+          continue;
+        } else {
+          if (childResult == Boolean.TRUE) {
+            trueCount++;
+          } else {
+            falseCount++;
+          }
+        }
+      }
+      if (childCount == 1) {
+        retval = falseCount == 0;
+      } else {
+        if (node instanceof Or_expressionContext) {
+          retval = trueCount > 0;
+        } else if (node instanceof And_expressionContext) {
+          retval = trueCount > 0 && falseCount == 0;
+        } else {
+          retval = trueCount > 0 && falseCount == 0;
+        }
+      }
+    }
+    return retval;
+  }
+
+}
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java
 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java
new file mode 100644
index 0000000..615e836
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java
@@ -0,0 +1,96 @@
+package org.apache.accumulo.access.grammar.antlr;
+
+import java.nio.charset.StandardCharsets;
+
+import org.antlr.v4.runtime.BailErrorStrategy;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CodePointCharStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ConsoleErrorListener;
+import org.antlr.v4.runtime.LexerNoViableAltException;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.apache.accumulo.access.IllegalAccessExpressionException;
+import org.apache.accumulo.access.grammars.AccessExpressionLexer;
+import org.apache.accumulo.access.grammars.AccessExpressionParser;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext;
+
+public class AccessExpressionAntlrParser {
+
+  private static class AccessExpressionLexerWithErrors extends 
AccessExpressionLexer {
+
+    private int errors = 0;
+    private final CharStream input;
+
+    public AccessExpressionLexerWithErrors(CharStream input) {
+      super(input);
+      this.input = input;
+    }
+
+    @Override
+    public void recover(LexerNoViableAltException e) {
+      System.out.println("Error in lexer. Expression: " + input + ", msg: " + 
e);
+      super.recover(e);
+      errors++;
+    }
+
+    @Override
+    public void recover(RecognitionException re) {
+      System.out.println("Error in lexer. Expression: " + input + ", msg: " + 
re);
+      super.recover(re);
+      errors++;
+    }
+
+    public int getErrorCount() {
+      return errors;
+    }
+
+  }
+
+  private static class ParserErrorListener extends ConsoleErrorListener {
+
+    int errors = 0;
+
+    @Override
+    public void syntaxError(Recognizer<?,?> recognizer, Object 
offendingSymbol, int line,
+        int charPositionInLine, String msg, RecognitionException e) {
+      super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, 
msg, e);
+      errors++;
+    }
+
+    public int getErrorCount() {
+      return errors;
+    }
+
+  }
+
+  public static Access_expressionContext parseAccessExpression(byte[] 
accessExpression)
+      throws IllegalAccessExpressionException {
+    return parseAccessExpression(new String(accessExpression, 
StandardCharsets.UTF_8));
+  }
+
+  public static Access_expressionContext parseAccessExpression(String 
accessExpression)
+      throws IllegalAccessExpressionException {
+    CodePointCharStream input = CharStreams.fromString(accessExpression);
+    AccessExpressionLexerWithErrors lexer = new 
AccessExpressionLexerWithErrors(input);
+    AccessExpressionParser parser = new AccessExpressionParser(new 
CommonTokenStream(lexer));
+    parser.setErrorHandler(new BailErrorStrategy());
+    parser.removeErrorListeners();
+    ParserErrorListener errorListener = new ParserErrorListener();
+    parser.addErrorListener(errorListener);
+    try {
+      int errors = 0;
+      Access_expressionContext ctx = parser.access_expression();
+      errors = lexer.getErrorCount();
+      errors += errorListener.getErrorCount();
+      if (errors > 0 || parser.getNumberOfSyntaxErrors() > 0 || ctx.exception 
!= null) {
+        throw new IllegalAccessExpressionException("Parse error", "", 0);
+      }
+      return ctx;
+    } catch (RuntimeException e1) {
+      throw new IllegalAccessExpressionException(e1.getMessage(), "", 0);
+    }
+  }
+
+}
diff --git 
a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java
 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java
new file mode 100644
index 0000000..799d18c
--- /dev/null
+++ 
b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java
@@ -0,0 +1,202 @@
+package org.apache.accumulo.access.grammar.antlr;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.antlr.v4.runtime.BailErrorStrategy;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CodePointCharStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ConsoleErrorListener;
+import org.antlr.v4.runtime.LexerNoViableAltException;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.apache.accumulo.access.AccessEvaluator;
+import org.apache.accumulo.access.AccessExpression;
+import org.apache.accumulo.access.Authorizations;
+import org.apache.accumulo.access.IllegalAccessExpressionException;
+import org.apache.accumulo.access.TestDataLoader;
+import org.apache.accumulo.access.TestDataLoader.ExpectedResult;
+import org.apache.accumulo.access.TestDataLoader.TestDataSet;
+import org.apache.accumulo.access.TestDataLoader.TestExpressions;
+import org.apache.accumulo.access.grammars.AccessExpressionLexer;
+import org.apache.accumulo.access.grammars.AccessExpressionParser;
+import 
org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext;
+import org.junit.jupiter.api.Test;
+
+public class Antlr4Tests {
+
+  private void testParse(String input) throws Exception {
+    CodePointCharStream expression = CharStreams.fromString(input);
+    final AtomicLong errors = new AtomicLong(0);
+    AccessExpressionLexer lexer = new AccessExpressionLexer(expression) {
+
+      @Override
+      public void recover(LexerNoViableAltException e) {
+        System.out.println("Error in lexer. Expression: " + expression + ", 
msg: " + e);
+        super.recover(e);
+        errors.incrementAndGet();
+      }
+
+      @Override
+      public void recover(RecognitionException re) {
+        System.out.println("Error in lexer. Expression: " + expression + ", 
msg: " + re);
+        super.recover(re);
+        errors.incrementAndGet();
+      }
+
+    };
+    AccessExpressionParser parser = new AccessExpressionParser(new 
CommonTokenStream(lexer));
+    parser.setErrorHandler(new BailErrorStrategy());
+    parser.removeErrorListeners();
+    parser.addErrorListener(new ConsoleErrorListener() {
+
+      @Override
+      public void syntaxError(Recognizer<?,?> recognizer, Object 
offendingSymbol, int line,
+          int charPositionInLine, String msg, RecognitionException e) {
+        super.syntaxError(recognizer, offendingSymbol, line, 
charPositionInLine, msg, e);
+        errors.incrementAndGet();
+      }
+
+    });
+    try {
+      Access_expressionContext ctx = parser.access_expression();
+      assertNull(ctx.exception);
+    } catch (RuntimeException e1) {
+      throw new AssertionError(e1);
+    }
+    assertEquals(0, errors.get());
+    assertEquals(0, parser.getNumberOfSyntaxErrors());
+  }
+
+  @Test
+  public void testCompareWithAccessExpressionImplParsing() throws Exception {
+    // This test checks that the parsing of the AccessExpressions in 
testdata.json
+    // using ANTLR have the same outcome as AccessExpression.of()
+    List<TestDataSet> testData = TestDataLoader.readTestData();
+    for (TestDataSet testSet : testData) {
+      for (TestExpressions test : testSet.tests) {
+        ExpectedResult result = test.expectedResult;
+        for (String cv : test.expressions) {
+          if (result == ExpectedResult.ERROR) {
+            assertThrows(IllegalAccessExpressionException.class, () -> 
AccessExpression.of(cv));
+            assertThrows(AssertionError.class, () -> testParse(cv));
+          } else {
+            AccessExpression.of(cv);
+            testParse(cv);
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testSimpleEvaluation() throws Exception {
+    String accessExpression = "(one&two)|(foo&bar)";
+    Authorizations auths = Authorizations.of("four", "three", "one", "two");
+    AccessExpressionAntlrEvaluator eval = new 
AccessExpressionAntlrEvaluator(List.of(auths));
+    assertTrue(eval.canAccess(accessExpression));
+  }
+
+  @Test
+  public void testSimpleEvaluationFailure() throws Exception {
+    String accessExpression = "(A&B&C)";
+    Authorizations auths = Authorizations.of("A", "C");
+    AccessExpressionAntlrEvaluator eval = new 
AccessExpressionAntlrEvaluator(List.of(auths));
+    assertFalse(eval.canAccess(accessExpression));
+  }
+
+  @Test
+  public void testCompareAntlrEvaluationAgainstAccessEvaluatorImpl() throws 
Exception {
+    // This test checks that the evaluation of the AccessExpressions in 
testdata.json
+    // using ANTLR have the same outcome as AccessEvaluatorImpl
+    List<TestDataSet> testData = TestDataLoader.readTestData();
+    for (TestDataSet testSet : testData) {
+
+      List<Authorizations> authSets =
+          
Stream.of(testSet.auths).map(Authorizations::of).collect(Collectors.toList());
+      AccessEvaluator evaluator = 
AccessEvaluator.builder().authorizations(authSets).build();
+      AccessExpressionAntlrEvaluator antlr = new 
AccessExpressionAntlrEvaluator(authSets);
+
+      for (TestExpressions test : testSet.tests) {
+        for (String expression : test.expressions) {
+          switch (test.expectedResult) {
+            case ACCESSIBLE:
+              assertTrue(evaluator.canAccess(expression), expression);
+              assertTrue(evaluator.canAccess(expression.getBytes(UTF_8)), 
expression);
+              assertTrue(evaluator.canAccess(AccessExpression.of(expression)), 
expression);
+              
assertTrue(evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+              
assertTrue(evaluator.canAccess(AccessExpression.of(expression).normalize()),
+                  expression);
+              assertEquals(expression,
+                  
AccessExpression.of(expression.getBytes(UTF_8)).getExpression());
+              assertEquals(expression, 
AccessExpression.of(expression).getExpression());
+
+              assertTrue(antlr.canAccess(expression), expression);
+              assertTrue(antlr.canAccess(expression.getBytes(UTF_8)), 
expression);
+              assertTrue(antlr.canAccess(AccessExpression.of(expression)), 
expression);
+              
assertTrue(antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+              
assertTrue(antlr.canAccess(AccessExpression.of(expression).normalize()), 
expression);
+
+              break;
+            case INACCESSIBLE:
+              assertFalse(evaluator.canAccess(expression), expression);
+              assertFalse(evaluator.canAccess(expression.getBytes(UTF_8)), 
expression);
+              
assertFalse(evaluator.canAccess(AccessExpression.of(expression)), expression);
+              
assertFalse(evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+              
assertFalse(evaluator.canAccess(AccessExpression.of(expression).normalize()),
+                  expression);
+              assertEquals(expression,
+                  
AccessExpression.of(expression.getBytes(UTF_8)).getExpression());
+              assertEquals(expression, 
AccessExpression.of(expression).getExpression());
+
+              assertFalse(antlr.canAccess(expression), expression);
+              assertFalse(antlr.canAccess(expression.getBytes(UTF_8)), 
expression);
+              assertFalse(antlr.canAccess(AccessExpression.of(expression)), 
expression);
+              
assertFalse(antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+              
assertFalse(antlr.canAccess(AccessExpression.of(expression).normalize()), 
expression);
+
+              break;
+            case ERROR:
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> evaluator.canAccess(expression), expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> evaluator.canAccess(expression.getBytes(UTF_8)), 
expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> evaluator.canAccess(AccessExpression.of(expression)), 
expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> 
evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> antlr.canAccess(expression), expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> antlr.canAccess(expression.getBytes(UTF_8)), 
expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> antlr.canAccess(AccessExpression.of(expression)), 
expression);
+              assertThrows(IllegalAccessExpressionException.class,
+                  () -> 
antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
+                  expression);
+              break;
+            default:
+              throw new IllegalArgumentException();
+          }
+        }
+      }
+    }
+
+  }
+}
diff --git a/contrib/antlr4/src/test/resources/specification.abnf 
b/contrib/antlr4/src/test/resources/specification.abnf
new file mode 100644
index 0000000..ccd1c38
--- /dev/null
+++ b/contrib/antlr4/src/test/resources/specification.abnf
@@ -0,0 +1,17 @@
+access-expression       = [expression] ; empty string is a valid access 
expression
+
+expression              =  (access-token / paren-expression) [and-expression / 
or-expression]
+
+paren-expression        =  "(" expression ")"
+
+and-expression          =  "&" (access-token / paren-expression) 
[and-expression]
+
+or-expression           =  "|" (access-token / paren-expression) 
[or-expression]
+
+access-token            = 1*( ALPHA / DIGIT / "_" / "-" / "." / ":" / slash )
+access-token            =/ DQUOTE 1*(utf8-subset / escaped) DQUOTE
+
+utf8-subset             = %x20-21 / %x23-5B / %x5D-7E / unicode-beyond-ascii ; 
utf8 minus '"' and '\'
+unicode-beyond-ascii    = %x0080-D7FF / %xE000-10FFFF
+escaped                 = "\" DQUOTE / "\\"
+slash                   = "/"
diff --git a/pom.xml b/pom.xml
index e2147ce..626572f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
   <properties>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
+    
<maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <version.jmh>1.36</version.jmh>
   </properties>
diff --git a/src/main/java/org/apache/accumulo/access/AccessEvaluator.java 
b/src/main/java/org/apache/accumulo/access/AccessEvaluator.java
index 576ad5b..0f579d0 100644
--- a/src/main/java/org/apache/accumulo/access/AccessEvaluator.java
+++ b/src/main/java/org/apache/accumulo/access/AccessEvaluator.java
@@ -19,7 +19,6 @@
 package org.apache.accumulo.access;
 
 import java.util.Collection;
-import java.util.List;
 
 /**
  * <p>
diff --git a/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java 
b/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java
index a5b59ec..5b3f62b 100644
--- a/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java
+++ b/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java
@@ -119,7 +119,6 @@ public class AccessEvaluatorTest {
       assertTrue(tests.expressions.length > 0);
 
       for (var expression : tests.expressions) {
-
         switch (tests.expectedResult) {
           case ACCESSIBLE:
             assertTrue(evaluator.canAccess(expression), expression);
diff --git a/src/test/resources/testdata.json b/src/test/resources/testdata.json
index be9c0d9..3654e49 100644
--- a/src/test/resources/testdata.json
+++ b/src/test/resources/testdata.json
@@ -198,7 +198,9 @@
           "(|a)",
           "&a&b(a&b)",
           "#A",
-          "#&"
+          "#&",
+          "(A&B)D&C",
+          "A&B(D&C)"
         ]
       }
     ]

Reply via email to