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}