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