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.MetapathExpression.ConversionFunction; 9 import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; 10 import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean; 11 import gov.nist.secauto.metaschema.core.metapath.impl.LazyCompilationMetapathExpression; 12 import gov.nist.secauto.metaschema.core.metapath.item.IItem; 13 import gov.nist.secauto.metaschema.core.metapath.item.ISequence; 14 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; 15 import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; 16 import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; 17 import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; 18 import gov.nist.secauto.metaschema.core.util.ObjectUtils; 19 20 import java.math.BigDecimal; 21 22 import edu.umd.cs.findbugs.annotations.NonNull; 23 import edu.umd.cs.findbugs.annotations.Nullable; 24 25 /** 26 * Supports compiling and executing Metapath expressions. 27 */ 28 public interface IMetapathExpression { 29 30 /** 31 * Identifies the expected type for a Metapath evaluation result. 32 */ 33 enum ResultType { 34 /** 35 * The result is expected to be a {@link BigDecimal} value. 36 */ 37 NUMBER(BigDecimal.class, sequence -> { 38 INumericItem numeric = FunctionUtils.toNumeric(sequence, true); 39 return numeric == null ? null : numeric.asDecimal(); 40 }), 41 /** 42 * The result is expected to be a {@link String} value. 43 */ 44 STRING(String.class, sequence -> { 45 IAnyAtomicItem item = ISequence.of(sequence.atomize()).getFirstItem(true); 46 return item == null ? "" : item.asString(); 47 }), 48 /** 49 * The result is expected to be a {@link Boolean} value. 50 */ 51 BOOLEAN(Boolean.class, sequence -> FnBoolean.fnBoolean(sequence).toBoolean()), 52 /** 53 * The result is expected to be an {@link IItem} value. 54 */ 55 ITEM(IItem.class, sequence -> sequence.getFirstItem(true)); 56 57 @NonNull 58 private final Class<?> clazz; 59 private final ConversionFunction converter; 60 61 ResultType(@NonNull Class<?> clazz, @NonNull ConversionFunction converter) { 62 this.clazz = clazz; 63 this.converter = converter; 64 } 65 66 /** 67 * Get the expected class for the result type. 68 * 69 * @return the expected class 70 * 71 */ 72 @NonNull 73 public Class<?> expectedClass() { 74 return clazz; 75 } 76 77 /** 78 * Convert the provided sequence to the expected type. 79 * 80 * @param <T> 81 * the Java type of the expected return value 82 * @param sequence 83 * the Metapath result sequence to convert 84 * @return the converted sequence as the expected type 85 * @throws TypeMetapathException 86 * if the provided sequence is incompatible with the expected result 87 * type 88 */ 89 @Nullable 90 public <T> T convert(@NonNull ISequence<?> sequence) { 91 try { 92 return ObjectUtils.asNullableType(converter.convert(sequence)); 93 } catch (ClassCastException ex) { 94 throw new InvalidTypeMetapathException(null, 95 String.format("Unable to cast to expected result type '%s' using expected type '%s'.", 96 name(), 97 expectedClass().getName()), 98 ex); 99 } 100 } 101 } 102 103 /** 104 * Get the Metapath expression identifying the current context node. 105 * 106 * @return the context expression 107 */ 108 @NonNull 109 static IMetapathExpression contextNode() { 110 return MetapathExpression.CONTEXT_NODE; 111 } 112 113 /** 114 * Compile a Metapath expression string. 115 * 116 * @param path 117 * the metapath expression 118 * @return the compiled expression object 119 * @throws MetapathException 120 * if an error occurred while compiling the Metapath expression 121 */ 122 @NonNull 123 static IMetapathExpression compile(@NonNull String path) { 124 return MetapathExpression.compile(path, StaticContext.instance()); 125 } 126 127 /** 128 * Compiles a Metapath expression string using the provided static context. 129 * 130 * @param path 131 * the metapath expression 132 * @param staticContext 133 * the static evaluation context 134 * @return the compiled expression object 135 * @throws MetapathException 136 * if an error occurred while compiling the Metapath expression 137 */ 138 @NonNull 139 static IMetapathExpression compile(@NonNull String path, @NonNull StaticContext staticContext) { 140 return MetapathExpression.compile(path, staticContext); 141 } 142 143 /** 144 * Gets a new Metapath expression that is compiled on use. 145 * <p> 146 * Lazy compilation may cause additional {@link MetapathException} errors at 147 * evaluation time, since compilation errors are not raised until evaluation. 148 * 149 * @param path 150 * the metapath expression 151 * @param staticContext 152 * the static evaluation context 153 * @return the expression object 154 */ 155 @NonNull 156 static IMetapathExpression lazyCompile(@NonNull String path, @NonNull StaticContext staticContext) { 157 return new LazyCompilationMetapathExpression(path, staticContext); 158 } 159 160 /** 161 * Get the original Metapath expression as a string. 162 * 163 * @return the expression 164 */ 165 @NonNull 166 String getPath(); 167 168 /** 169 * Get the static context used to compile this Metapath. 170 * 171 * @return the static context 172 */ 173 @NonNull 174 StaticContext getStaticContext(); 175 176 /** 177 * Evaluate this Metapath expression without a specific focus. The required 178 * result type will be determined by the {@code resultType} argument. 179 * 180 * @param <T> 181 * the expected result type 182 * @param resultType 183 * the type of result to produce 184 * @return the converted result 185 * @throws TypeMetapathException 186 * if the provided sequence is incompatible with the requested result 187 * type 188 * @throws MetapathException 189 * if an error occurred during evaluation 190 * @see ResultType#convert(ISequence) 191 */ 192 @Nullable 193 default <T> T evaluateAs(@NonNull ResultType resultType) { 194 return evaluateAs(null, resultType); 195 } 196 197 /** 198 * Evaluate this Metapath expression using the provided {@code focus} as the 199 * initial evaluation context. The required result type will be determined by 200 * the {@code resultType} argument. 201 * 202 * @param <T> 203 * the expected result type 204 * @param focus 205 * the focus of the expression 206 * @param resultType 207 * the type of result to produce 208 * @return the converted result 209 * @throws TypeMetapathException 210 * if the provided sequence is incompatible with the requested result 211 * type 212 * @throws MetapathException 213 * if an error occurred during evaluation 214 * @see ResultType#convert(ISequence) 215 */ 216 @Nullable 217 default <T> T evaluateAs( 218 @Nullable IItem focus, 219 @NonNull ResultType resultType) { 220 ISequence<?> result = evaluate(focus); 221 return resultType.convert(result); 222 } 223 224 /** 225 * Evaluate this Metapath expression using the provided {@code focus} as the 226 * initial evaluation context. The specific result type will be determined by 227 * the {@code resultType} argument. 228 * <p> 229 * This variant allow for reuse of a provided {@code dynamicContext}. 230 * 231 * @param <T> 232 * the expected result type 233 * @param focus 234 * the outer focus of the expression 235 * @param resultType 236 * the type of result to produce 237 * @param dynamicContext 238 * the dynamic context to use for evaluation 239 * @return the converted result 240 * @throws TypeMetapathException 241 * if the provided sequence is incompatible with the requested result 242 * type 243 * @throws MetapathException 244 * if an error occurred during evaluation 245 * @see ResultType#convert(ISequence) 246 */ 247 @Nullable 248 default <T> T evaluateAs( 249 @Nullable IItem focus, 250 @NonNull ResultType resultType, 251 @NonNull DynamicContext dynamicContext) { 252 ISequence<?> result = evaluate(focus, dynamicContext); 253 return resultType.convert(result); 254 } 255 256 /** 257 * Evaluate this Metapath expression without a specific focus. 258 * 259 * @param <T> 260 * the type of items contained in the resulting sequence 261 * @return a sequence of Metapath items representing the result of the 262 * evaluation 263 * @throws MetapathException 264 * if an error occurred during evaluation 265 */ 266 @NonNull 267 default <T extends IItem> ISequence<T> evaluate() { 268 return evaluate((IItem) null); 269 } 270 271 /** 272 * Evaluate this Metapath expression using the provided {@code focus} as the 273 * initial evaluation context. 274 * 275 * @param <T> 276 * the type of items contained in the resulting sequence 277 * @param focus 278 * the outer focus of the expression 279 * @return a sequence of Metapath items representing the result of the 280 * evaluation 281 * @throws MetapathException 282 * if an error occurred during evaluation 283 */ 284 @NonNull 285 default <T extends IItem> ISequence<T> evaluate( 286 @Nullable IItem focus) { 287 return evaluate(focus, new DynamicContext(getStaticContext())); 288 } 289 290 /** 291 * Evaluate this Metapath expression using the provided {@code focus} as the 292 * initial evaluation context. 293 * <p> 294 * This variant allow for reuse of a provided {@code dynamicContext}. 295 * 296 * @param <T> 297 * the type of items contained in the resulting sequence 298 * @param focus 299 * the outer focus of the expression 300 * @param dynamicContext 301 * the dynamic context to use for evaluation 302 * @return a sequence of Metapath items representing the result of the 303 * evaluation 304 * @throws MetapathException 305 * if an error occurred during evaluation 306 */ 307 @NonNull 308 <T extends IItem> ISequence<T> evaluate( 309 @Nullable IItem focus, 310 @NonNull DynamicContext dynamicContext); 311 }