1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath.impl;
7   
8   import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9   import gov.nist.secauto.metaschema.core.metapath.IExpression;
10  import gov.nist.secauto.metaschema.core.metapath.InvalidMetapathGrammarException;
11  import gov.nist.secauto.metaschema.core.metapath.StaticContext;
12  import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException;
13  import gov.nist.secauto.metaschema.core.metapath.antlr.FailingErrorListener;
14  import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10;
15  import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10Lexer;
16  import gov.nist.secauto.metaschema.core.metapath.antlr.ParseTreePrinter;
17  import gov.nist.secauto.metaschema.core.metapath.cst.BuildCSTVisitor;
18  import gov.nist.secauto.metaschema.core.metapath.cst.CSTPrinter;
19  import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem;
20  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
21  import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
22  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
23  
24  import org.antlr.v4.runtime.CharStreams;
25  import org.antlr.v4.runtime.CommonTokenStream;
26  import org.antlr.v4.runtime.DefaultErrorStrategy;
27  import org.antlr.v4.runtime.Parser;
28  import org.antlr.v4.runtime.misc.ParseCancellationException;
29  import org.antlr.v4.runtime.tree.ParseTree;
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.io.PrintStream;
36  import java.nio.charset.StandardCharsets;
37  
38  import edu.umd.cs.findbugs.annotations.NonNull;
39  import edu.umd.cs.findbugs.annotations.Nullable;
40  
41  /**
42   * Supports compiling and executing Metapath expressions.
43   */
44  @SuppressWarnings({
45      "PMD.CouplingBetweenObjects" // necessary since this class aggregates functionality
46  })
47  public class MetapathExpression
48      extends AbstractMetapathExpression {
49  
50    /**
51     * The Metapath expression identifying the current context node.
52     */
53    @NonNull
54    public static final MetapathExpression CONTEXT_METAPATH
55        = new MetapathExpression(".", ContextItem.instance(), StaticContext.instance());
56    private static final Logger LOGGER = LogManager.getLogger(MetapathExpression.class);
57    @NonNull
58    private final IExpression expression;
59  
60    /**
61     * Compiles a Metapath expression string using the provided static context.
62     *
63     * @param path
64     *          the metapath expression
65     * @param context
66     *          the static evaluation context
67     * @return the compiled expression object
68     * @throws InvalidMetapathGrammarException
69     *           if an error occurred while compiling the Metapath expression
70     */
71    @NonNull
72    public static MetapathExpression compile(@NonNull String path, @NonNull StaticContext context) {
73      @NonNull
74      MetapathExpression retval;
75      if (".".equals(path)) {
76        retval = CONTEXT_METAPATH;
77      } else {
78        try {
79          Metapath10 parser = newParser(path);
80          ParseTree tree = ObjectUtils.notNull(parser.metapath());
81          logAst(tree);
82          IExpression expr = new BuildCSTVisitor(context).visit(tree);
83          logCst(expr);
84          retval = new MetapathExpression(path, expr, context);
85        } catch (StaticMetapathException ex) {
86          String message = ex.getMessageText();
87          throw new InvalidMetapathGrammarException(
88              String.format("Unable to compile path '%s'.%s", path, message == null ? "" : " " + message),
89              ex);
90        } catch (ParseCancellationException ex) {
91          String msg = String.format("Unable to compile Metapath '%s'", path);
92          if (LOGGER.isDebugEnabled()) {
93            LOGGER.atDebug().withThrowable(ex).log(msg);
94          }
95          throw new InvalidMetapathGrammarException(msg, ex);
96        }
97      }
98      return retval;
99    }
100 
101   private static void logCst(@NonNull IExpression expr) {
102     if (LOGGER.isDebugEnabled()) {
103       LOGGER.atDebug().log(String.format("Metapath CST:%n%s", CSTPrinter.toString(expr)));
104     }
105   }
106 
107   private static void logAst(@NonNull ParseTree tree) {
108     if (LOGGER.isDebugEnabled()) {
109       try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
110         try (PrintStream ps = new PrintStream(os, true, StandardCharsets.UTF_8)) {
111           ParseTreePrinter printer = new ParseTreePrinter(ps);
112           printer.print(tree, Metapath10.ruleNames);
113           ps.flush();
114         }
115         LOGGER.atDebug().log(String.format("Metapath AST:%n%s", os.toString(StandardCharsets.UTF_8)));
116       } catch (IOException ex) {
117         LOGGER.atError().withThrowable(ex).log("An unexpected error occurred while closing the steam.");
118       }
119     }
120   }
121 
122   @NonNull
123   private static Metapath10 newParser(@NonNull String path) {
124     Metapath10Lexer lexer = new Metapath10Lexer(CharStreams.fromString(path));
125     lexer.removeErrorListeners();
126     lexer.addErrorListener(new FailingErrorListener());
127 
128     CommonTokenStream tokens = new CommonTokenStream(lexer);
129     Metapath10 parser = new Metapath10(tokens);
130     parser.removeErrorListeners();
131     parser.addErrorListener(new FailingErrorListener());
132     parser.setErrorHandler(new DefaultErrorStrategy() {
133 
134       @Override
135       public void sync(Parser recognizer) {
136         // disable
137       }
138     });
139     return parser;
140   }
141 
142   /**
143    * Construct a new Metapath expression.
144    *
145    * @param path
146    *          the Metapath as a string
147    * @param expr
148    *          the Metapath as a compiled abstract syntax tree (AST)
149    * @param staticContext
150    *          the static evaluation context
151    */
152   protected MetapathExpression(
153       @NonNull String path,
154       @NonNull IExpression expr,
155       @NonNull StaticContext staticContext) {
156     super(path, staticContext);
157     this.expression = expr;
158   }
159 
160   @Override
161   protected IExpression getExpression() {
162     return expression;
163   }
164 
165   /**
166    * Get the compiled compact syntax tree (CST) representation of the Metapath.
167    *
168    * @return the Metapath CST
169    */
170   @NonNull
171   protected IExpression getCSTNode() {
172     return expression;
173   }
174 
175   @Override
176   public String toString() {
177     return getPath();
178   }
179 
180   @Override
181   @NonNull
182   public <T extends IItem> ISequence<T> evaluate(
183       @Nullable IItem focus,
184       @NonNull DynamicContext dynamicContext) {
185     dynamicContext.pushExecutionStack(this);
186     try {
187       return ObjectUtils.asType(getCSTNode().accept(dynamicContext, ISequence.of(focus)).reusable());
188     } finally {
189       dynamicContext.popExecutionStack(this);
190     }
191   }
192 }