001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.metapath; 028 029import gov.nist.secauto.metaschema.core.metapath.antlr.metapath10Lexer; 030import gov.nist.secauto.metaschema.core.metapath.antlr.metapath10Parser; 031import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; 032import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean; 033import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; 034import gov.nist.secauto.metaschema.core.metapath.item.IItem; 035import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; 036import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem; 037import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; 038import gov.nist.secauto.metaschema.core.util.ObjectUtils; 039 040import org.antlr.v4.runtime.CharStreams; 041import org.antlr.v4.runtime.CommonTokenStream; 042import org.antlr.v4.runtime.misc.ParseCancellationException; 043import org.antlr.v4.runtime.tree.ParseTree; 044import org.apache.logging.log4j.LogManager; 045import org.apache.logging.log4j.Logger; 046 047import java.io.ByteArrayOutputStream; 048import java.io.IOException; 049import java.io.PrintStream; 050import java.nio.charset.StandardCharsets; 051import java.util.Arrays; 052 053import edu.umd.cs.findbugs.annotations.NonNull; 054import edu.umd.cs.findbugs.annotations.Nullable; 055 056public class MetapathExpression { 057 058 public enum ResultType { 059 NUMBER, 060 STRING, 061 BOOLEAN, 062 SEQUENCE, 063 NODE; 064 } 065 066 private static final Logger LOGGER = LogManager.getLogger(MetapathExpression.class); 067 068 @NonNull 069 public static final MetapathExpression CONTEXT_NODE = new MetapathExpression(".", ContextItem.instance()); 070 071 private final String path; 072 @NonNull 073 private final IExpression node; 074 075 /** 076 * Compiles a Metapath expression string. 077 * 078 * @param path 079 * the metapath expression 080 * @return the compiled expression object 081 * @throws MetapathException 082 * if an error occurred while compiling the Metapath expression 083 */ 084 @NonNull 085 public static MetapathExpression compile(@NonNull String path) { 086 @NonNull MetapathExpression retval; 087 if (".".equals(path)) { 088 retval = CONTEXT_NODE; 089 } else { 090 try { 091 metapath10Lexer lexer = new metapath10Lexer(CharStreams.fromString(path)); 092 CommonTokenStream tokens = new CommonTokenStream(lexer); 093 metapath10Parser parser = new metapath10Parser(tokens); 094 parser.removeErrorListeners(); 095 parser.addErrorListener(new FailingErrorListener()); 096 097 ParseTree tree = ObjectUtils.notNull(parser.expr()); 098 099 if (LOGGER.isDebugEnabled()) { 100 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 101 try (PrintStream ps = new PrintStream(os, true, StandardCharsets.UTF_8)) { 102 CSTPrinter printer = new CSTPrinter(ps); 103 printer.print(tree, Arrays.asList(metapath10Parser.ruleNames)); 104 ps.flush(); 105 } 106 LOGGER.atDebug().log(String.format("Metapath CST:%n%s", os.toString(StandardCharsets.UTF_8))); 107 } catch (IOException ex) { 108 LOGGER.atError().withThrowable(ex).log("An unexpected error occured while closing the steam."); 109 } 110 } 111 112 IExpression expr = new BuildAstVisitor().visit(tree); 113 114 if (LOGGER.isDebugEnabled()) { 115 LOGGER.atDebug().log(String.format("Metapath AST:%n%s", ASTPrinter.instance().visit(expr))); 116 } 117 retval = new MetapathExpression(path, expr); 118 } catch (MetapathException | ParseCancellationException ex) { 119 String msg = String.format("Unable to compile Metapath '%s'", path); 120 LOGGER.atError().withThrowable(ex).log(msg); 121 throw new MetapathException(msg, ex); 122 } 123 } 124 return retval; 125 } 126 127 /** 128 * Construct a new Metapath expression. 129 * 130 * @param path 131 * the Metapath as a string 132 * @param expr 133 * the Metapath as a compiled abstract syntax tree (AST) 134 */ 135 protected MetapathExpression(@NonNull String path, @NonNull IExpression expr) { 136 this.path = path; 137 this.node = expr; 138 } 139 140 /** 141 * Get the original Metapath expression as a string. 142 * 143 * @return the expression 144 */ 145 public String getPath() { 146 return path; 147 } 148 149 /** 150 * Get the compiled abstract syntax tree (AST) representation of the Metapath. 151 * 152 * @return the Metapath AST 153 */ 154 @NonNull 155 protected IExpression getASTNode() { 156 return node; 157 } 158 159 @Override 160 public String toString() { 161 return ASTPrinter.instance().visit(getASTNode()); 162 } 163 164 /** 165 * Evaluate this Metapath expression without a specific focus. The required 166 * result type will be determined by the {@code resultType} argument. 167 * 168 * @param <T> 169 * the expected result type 170 * @param resultType 171 * the type of result to produce 172 * @return the converted result 173 * @throws TypeMetapathException 174 * if the provided sequence is incompatible with the requested result 175 * type 176 * @throws MetapathException 177 * if an error occurred during evaluation 178 * @see #toResultType(ISequence, ResultType) 179 */ 180 @Nullable 181 public <T> T evaluateAs(@NonNull ResultType resultType) { 182 return evaluateAs(null, resultType); 183 } 184 185 /** 186 * Evaluate this Metapath expression using the provided {@code focus} as the 187 * initial evaluation context. The required result type will be determined by 188 * the {@code resultType} argument. 189 * 190 * @param <T> 191 * the expected result type 192 * @param focus 193 * the outer focus of the expression 194 * @param resultType 195 * the type of result to produce 196 * @return the converted result 197 * @throws TypeMetapathException 198 * if the provided sequence is incompatible with the requested result 199 * type 200 * @throws MetapathException 201 * if an error occurred during evaluation 202 * @see #toResultType(ISequence, ResultType) 203 */ 204 @Nullable 205 public <T> T evaluateAs( 206 @Nullable IItem focus, 207 @NonNull ResultType resultType) { 208 ISequence<?> result = evaluate(focus); 209 return toResultType(result, resultType); 210 } 211 212 /** 213 * Evaluate this Metapath expression using the provided {@code focus} as the 214 * initial evaluation context. The specific result type will be determined by 215 * the {@code resultType} argument. 216 * <p> 217 * This variant allow for reuse of a provided {@code dynamicContext}. 218 * 219 * @param <T> 220 * the expected result type 221 * @param focus 222 * the outer focus of the expression 223 * @param resultType 224 * the type of result to produce 225 * @param dynamicContext 226 * the dynamic context to use for evaluation 227 * @return the converted result 228 * @throws TypeMetapathException 229 * if the provided sequence is incompatible with the requested result 230 * type 231 * @throws MetapathException 232 * if an error occurred during evaluation 233 * @see #toResultType(ISequence, ResultType) 234 */ 235 @Nullable 236 public <T> T evaluateAs( 237 @NonNull IItem focus, 238 @NonNull ResultType resultType, 239 @NonNull DynamicContext dynamicContext) { 240 ISequence<?> result = evaluate(focus, dynamicContext); 241 return toResultType(result, resultType); 242 } 243 244 /** 245 * Converts the provided {@code sequence} to the requested {@code resultType}. 246 * <p> 247 * The {@code resultType} determines the returned result, which is derived from 248 * the evaluation result sequence, as follows: 249 * <ul> 250 * <li>BOOLEAN - the effective boolean result is produced using 251 * {@link FnBoolean#fnBoolean(ISequence)}.</li> 252 * <li>NODE - the first result item in the sequence is returned.</li> 253 * <li>NUMBER - the sequence is cast to a number using 254 * {@link IDecimalItem#cast(IAnyAtomicItem)}.</li> 255 * <li>SEQUENCE - the evaluation result sequence.</li> 256 * <li>STRING - the string value of the first result item in the sequence.</li> 257 * </ul> 258 * 259 * @param <T> 260 * the requested return value 261 * @param sequence 262 * the sequence to convert 263 * @param resultType 264 * the type of result to produce 265 * @return the converted result 266 * @throws TypeMetapathException 267 * if the provided sequence is incompatible with the requested result 268 * type 269 */ 270 @SuppressWarnings("PMD.NullAssignment") // for readability 271 @Nullable 272 protected <T> T toResultType(@NonNull ISequence<?> sequence, @NonNull ResultType resultType) { 273 Object result; 274 switch (resultType) { 275 case BOOLEAN: 276 result = FnBoolean.fnBoolean(sequence).toBoolean(); 277 break; 278 case NODE: 279 result = FunctionUtils.getFirstItem(sequence, true); 280 break; 281 case NUMBER: 282 INumericItem numeric = FunctionUtils.toNumeric(sequence, true); 283 result = numeric == null ? null : numeric.asDecimal(); 284 break; 285 case SEQUENCE: 286 result = sequence; 287 break; 288 case STRING: 289 IItem item = FunctionUtils.getFirstItem(sequence, true); 290 result = item == null ? "" : FnData.fnDataItem(item).asString(); 291 break; 292 default: 293 throw new InvalidTypeMetapathException(null, String.format("unsupported result type '%s'", resultType.name())); 294 } 295 296 @SuppressWarnings("unchecked") T retval = (T) result; 297 return retval; 298 } 299 300 /** 301 * Evaluate this Metapath expression without a specific focus. 302 * 303 * @param <T> 304 * the type of items contained in the resulting sequence 305 * @return a sequence of Metapath items representing the result of the 306 * evaluation 307 * @throws MetapathException 308 * if an error occurred during evaluation 309 */ 310 @NonNull 311 public <T extends IItem> ISequence<T> evaluate() { 312 return evaluate(null); 313 } 314 315 /** 316 * Evaluate this Metapath expression using the provided {@code focus} as the 317 * initial evaluation context. 318 * 319 * @param <T> 320 * the type of items contained in the resulting sequence 321 * @param focus 322 * the outer focus of the expression 323 * @return a sequence of Metapath items representing the result of the 324 * evaluation 325 * @throws MetapathException 326 * if an error occurred during evaluation 327 */ 328 @SuppressWarnings("unchecked") 329 @NonNull 330 public <T extends IItem> ISequence<T> evaluate( 331 @Nullable IItem focus) { 332 return (ISequence<T>) evaluate( 333 focus, 334 StaticContext.builder() 335 .build().newDynamicContext()); 336 } 337 338 /** 339 * Evaluate this Metapath expression using the provided {@code focus} as the 340 * initial evaluation context. 341 * <p> 342 * This variant allow for reuse of a provided {@code dynamicContext}. 343 * 344 * @param <T> 345 * the type of items contained in the resulting sequence 346 * @param focus 347 * the outer focus of the expression 348 * @param dynamicContext 349 * the dynamic context to use for evaluation 350 * @return a sequence of Metapath items representing the result of the 351 * evaluation 352 * @throws MetapathException 353 * if an error occurred during evaluation 354 */ 355 @SuppressWarnings("unchecked") 356 @NonNull 357 public <T extends IItem> ISequence<T> evaluate( 358 @Nullable IItem focus, 359 @NonNull DynamicContext dynamicContext) { 360 try { 361 return (ISequence<T>) getASTNode().accept(dynamicContext, ISequence.of(focus)); 362 } catch (MetapathException ex) { // NOPMD - intentional 363 throw new MetapathException( 364 String.format("An error occurred while evaluating the expression '%s'.", getPath()), ex); 365 } 366 } 367}