001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.core.metapath; 007 008import java.math.BigDecimal; 009 010import dev.metaschema.core.metapath.function.FunctionUtils; 011import dev.metaschema.core.metapath.function.library.FnBoolean; 012import dev.metaschema.core.metapath.impl.LazyCompilationMetapathExpression; 013import dev.metaschema.core.metapath.impl.MetapathExpression; 014import dev.metaschema.core.metapath.item.IItem; 015import dev.metaschema.core.metapath.item.ISequence; 016import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem; 017import dev.metaschema.core.metapath.item.atomic.INumericItem; 018import dev.metaschema.core.metapath.type.InvalidTypeMetapathException; 019import dev.metaschema.core.metapath.type.TypeMetapathException; 020import dev.metaschema.core.util.ObjectUtils; 021import edu.umd.cs.findbugs.annotations.NonNull; 022import edu.umd.cs.findbugs.annotations.Nullable; 023 024/** 025 * Supports compiling and executing Metapath expressions. 026 */ 027public interface IMetapathExpression extends IExpression { 028 029 /** 030 * Identifies the expected type for a Metapath evaluation result. 031 */ 032 enum ResultType { 033 /** 034 * The result is expected to be a {@link BigDecimal} value. 035 */ 036 NUMBER(BigDecimal.class, sequence -> { 037 IItem item = sequence.getFirstItem(true); 038 if (item == null) { 039 return null; 040 } 041 IAnyAtomicItem atomicItem = ISequence.getFirstItem(item.atomize(), true); 042 if (atomicItem == null) { 043 throw new InvalidTypeMetapathException(item, "Unable to cast to numeric: atomization returned null"); 044 } 045 INumericItem numeric = FunctionUtils.castToNumeric(atomicItem); 046 return numeric.asDecimal(); 047 }), 048 /** 049 * The result is expected to be a {@link String} value. 050 */ 051 STRING(String.class, sequence -> { 052 IAnyAtomicItem item = ISequence.of(sequence.atomize()).getFirstItem(true); 053 return item == null ? "" : item.asString(); 054 }), 055 /** 056 * The result is expected to be a {@link Boolean} value. 057 */ 058 BOOLEAN(Boolean.class, sequence -> FnBoolean.fnBoolean(sequence).toBoolean()), 059 /** 060 * The result is expected to be an {@link IItem} value. 061 */ 062 ITEM(IItem.class, sequence -> sequence.getFirstItem(true)); 063 064 @NonNull 065 private final Class<?> clazz; 066 private final ConversionFunction converter; 067 068 ResultType(@NonNull Class<?> clazz, @NonNull ConversionFunction converter) { 069 this.clazz = clazz; 070 this.converter = converter; 071 } 072 073 /** 074 * Get the expected class for the result type. 075 * 076 * @return the expected class 077 * 078 */ 079 @NonNull 080 public Class<?> expectedClass() { 081 return clazz; 082 } 083 084 /** 085 * Convert the provided sequence to the expected type. 086 * 087 * @param <T> 088 * the Java type of the expected return value 089 * @param sequence 090 * the Metapath result sequence to convert 091 * @return the converted sequence as the expected type 092 * @throws TypeMetapathException 093 * if the provided sequence is incompatible with the expected result 094 * type 095 */ 096 // TODO: trace exceptions thrown to ensure the Javadoc matches; should be static 097 // type error 098 @Nullable 099 public <T> T convert(@NonNull ISequence<?> sequence) { 100 try { 101 return ObjectUtils.asNullableType(converter.convert(sequence)); 102 } catch (ClassCastException ex) { 103 throw new InvalidTypeMetapathException( 104 null, 105 String.format("Unable to cast type '%s' to expected result type '%s'.", 106 expectedClass().getName(), 107 name()), 108 ex); 109 } 110 } 111 112 @FunctionalInterface 113 interface ConversionFunction { 114 @Nullable 115 Object convert(@NonNull ISequence<?> sequence); 116 } 117 } 118 119 /** 120 * Get the Metapath expression identifying the current context node. 121 * 122 * @return the context expression 123 */ 124 @NonNull 125 static IMetapathExpression contextNode() { 126 return MetapathExpression.CONTEXT_METAPATH; 127 } 128 129 /** 130 * Compile a Metapath expression string. 131 * 132 * @param path 133 * the metapath expression 134 * @return the compiled expression object 135 * @throws InvalidMetapathGrammarException 136 * if an error occurred while compiling the Metapath expression 137 */ 138 @NonNull 139 static IMetapathExpression compile(@NonNull String path) { 140 return compile(path, StaticContext.instance()); 141 } 142 143 /** 144 * Compiles a Metapath expression string using the provided static context. 145 * 146 * @param path 147 * the metapath expression 148 * @param staticContext 149 * the static evaluation context 150 * @return the compiled expression object 151 * @throws InvalidMetapathGrammarException 152 * if an error occurred while compiling the Metapath expression 153 */ 154 @NonNull 155 static IMetapathExpression compile(@NonNull String path, @NonNull StaticContext staticContext) { 156 return MetapathExpression.compile(path, staticContext); 157 } 158 159 /** 160 * Gets a new Metapath expression that is compiled on use. 161 * <p> 162 * Lazy compilation may cause additional {@link MetapathException} errors at 163 * evaluation time, since compilation errors are not raised until evaluation. 164 * 165 * @param path 166 * the metapath expression 167 * @param staticContext 168 * the static evaluation context 169 * @return the expression object 170 */ 171 @NonNull 172 static IMetapathExpression lazyCompile(@NonNull String path, @NonNull StaticContext staticContext) { 173 return new LazyCompilationMetapathExpression(path, staticContext); 174 } 175 176 /** 177 * Get the original Metapath expression as a string. 178 * 179 * @return the expression 180 */ 181 @Override 182 @NonNull 183 String getPath(); 184 185 /** 186 * Get the static context used to compile this Metapath. 187 * 188 * @return the static context 189 */ 190 @NonNull 191 StaticContext getStaticContext(); 192 193 /** 194 * Evaluate this Metapath expression without a specific focus. The required 195 * result type will be determined by the {@code resultType} argument. 196 * 197 * @param <T> 198 * the expected result type 199 * @param resultType 200 * the type of result to produce 201 * @return the converted result 202 * @throws TypeMetapathException 203 * if the provided sequence is incompatible with the requested result 204 * type 205 * @throws MetapathException 206 * if an error occurred during evaluation 207 * @see ResultType#convert(ISequence) 208 */ 209 @Nullable 210 default <T> T evaluateAs(@NonNull ResultType resultType) { 211 return evaluateAs(null, resultType); 212 } 213 214 /** 215 * Evaluate this Metapath expression using the provided {@code focus} as the 216 * initial evaluation context. The required result type will be determined by 217 * the {@code resultType} argument. 218 * 219 * @param <T> 220 * the expected result type 221 * @param focus 222 * the focus of the expression 223 * @param resultType 224 * the type of result to produce 225 * @return the converted result 226 * @throws TypeMetapathException 227 * if the provided sequence is incompatible with the requested result 228 * type 229 * @throws MetapathException 230 * if an error occurred during evaluation 231 * @see ResultType#convert(ISequence) 232 */ 233 @Nullable 234 default <T> T evaluateAs( 235 @Nullable IItem focus, 236 @NonNull ResultType resultType) { 237 ISequence<?> result = evaluate(focus); 238 return resultType.convert(result); 239 } 240 241 /** 242 * Evaluate this Metapath expression using the provided {@code focus} as the 243 * initial evaluation context. The specific result type will be determined by 244 * the {@code resultType} argument. 245 * <p> 246 * This variant allow for reuse of a provided {@code dynamicContext}. 247 * 248 * @param <T> 249 * the expected result type 250 * @param focus 251 * the outer focus of the expression 252 * @param resultType 253 * the type of result to produce 254 * @param dynamicContext 255 * the dynamic context to use for evaluation 256 * @return the converted result 257 * @throws TypeMetapathException 258 * if the provided sequence is incompatible with the requested result 259 * type 260 * @throws MetapathException 261 * if an error occurred during evaluation 262 * @see ResultType#convert(ISequence) 263 */ 264 @Nullable 265 default <T> T evaluateAs( 266 @Nullable IItem focus, 267 @NonNull ResultType resultType, 268 @NonNull DynamicContext dynamicContext) { 269 ISequence<?> result = evaluate(focus, dynamicContext); 270 return resultType.convert(result); 271 } 272 273 /** 274 * Evaluate this Metapath expression without a specific focus. 275 * 276 * @param <T> 277 * the type of items contained in the resulting sequence 278 * @return a sequence of Metapath items representing the result of the 279 * evaluation 280 * @throws MetapathException 281 * if an error occurred during evaluation 282 */ 283 @NonNull 284 default <T extends IItem> ISequence<T> evaluate() { 285 return evaluate((IItem) null); 286 } 287 288 /** 289 * Evaluate this Metapath expression using the provided {@code focus} as the 290 * initial evaluation context. 291 * 292 * @param <T> 293 * the type of items contained in the resulting sequence 294 * @param focus 295 * the outer focus of the expression 296 * @return a sequence of Metapath items representing the result of the 297 * evaluation 298 * @throws MetapathException 299 * if an error occurred during evaluation 300 */ 301 @NonNull 302 default <T extends IItem> ISequence<T> evaluate( 303 @Nullable IItem focus) { 304 return evaluate(focus, new DynamicContext(getStaticContext())); 305 } 306 307 /** 308 * Evaluate this Metapath expression using the provided {@code focus} as the 309 * initial evaluation context. 310 * <p> 311 * This variant allow for reuse of a provided {@code dynamicContext}. 312 * 313 * @param <T> 314 * the type of items contained in the resulting sequence 315 * @param focus 316 * the outer focus of the expression 317 * @param dynamicContext 318 * the dynamic context to use for evaluation 319 * @return a sequence of Metapath items representing the result of the 320 * evaluation 321 * @throws MetapathException 322 * if an error occurred during evaluation 323 */ 324 @NonNull 325 <T extends IItem> ISequence<T> evaluate( 326 @Nullable IItem focus, 327 @NonNull DynamicContext dynamicContext); 328 329}