1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst.items;
7   
8   import java.util.List;
9   import java.util.Map;
10  import java.util.stream.Collectors;
11  
12  import dev.metaschema.core.metapath.DynamicContext;
13  import dev.metaschema.core.metapath.IExpression;
14  import dev.metaschema.core.metapath.cst.AbstractExpression;
15  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
16  import dev.metaschema.core.metapath.item.ICollectionValue;
17  import dev.metaschema.core.metapath.item.ISequence;
18  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
19  import dev.metaschema.core.metapath.item.function.IMapItem;
20  import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
21  import dev.metaschema.core.util.ObjectUtils;
22  import edu.umd.cs.findbugs.annotations.NonNull;
23  
24  /**
25   * An implementation of the
26   * <a href="https://www.w3.org/TR/xpath-31/#id-map-constructors">Map
27   * Constructor</a> supporting the creation of a Metapath {@link IMapItem}.
28   */
29  public class MapConstructor
30      extends AbstractExpression {
31    @NonNull
32    private final List<MapConstructor.Entry> entries;
33  
34    /**
35     * Construct a new map constructor expression that uses the provided entry
36     * expressions to initialize the map entries.
37     *
38     * @param text
39     *          the parsed text of the expression
40     * @param entries
41     *          the expressions used to produce the map entries
42     */
43    public MapConstructor(@NonNull String text, @NonNull List<MapConstructor.Entry> entries) {
44      super(text);
45      this.entries = entries;
46    }
47  
48    @Override
49    public List<MapConstructor.Entry> getChildren() {
50      return entries;
51    }
52  
53    @Override
54    protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
55      return IMapItem.ofCollection(
56          ObjectUtils.notNull(getChildren().stream()
57              .map(item -> {
58                IExpression keyExpression = item.getKeyExpression();
59                IAnyAtomicItem key = ISequence.of(keyExpression.accept(dynamicContext, focus).atomize())
60                    .getFirstItem(true);
61                if (key == null) {
62                  throw new InvalidTypeMetapathException(
63                      null,
64                      String.format("The expression '%s' did not result in a single key atomic value.",
65                          keyExpression.toCSTString()))
66                              .registerEvaluationContext(dynamicContext);
67                }
68                ICollectionValue value = item.getValueExpression().accept(dynamicContext, focus).toCollectionValue();
69  
70                return IMapItem.entry(key, value);
71              }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))))
72          .toSequence();
73    }
74  
75    @Override
76    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
77      return visitor.visitMapConstructor(this, context);
78    }
79  
80    /**
81     * A map entry expression used to produce an entry in a {@link IMapItem}.
82     */
83    public static class Entry
84        extends AbstractExpression {
85      @NonNull
86      private final IExpression keyExpression;
87      @NonNull
88      private final IExpression valueExpression;
89  
90      /**
91       * Construct a new map entry expression using the provided key and value
92       * expressions.
93       *
94       * @param text
95       *          the parsed text of the expression
96       * @param keyExpression
97       *          the expression used to get the map entry key
98       * @param valueExpression
99       *          the expression used to get the map entry value
100      */
101     public Entry(@NonNull String text, @NonNull IExpression keyExpression, @NonNull IExpression valueExpression) {
102       super(text);
103       this.keyExpression = keyExpression;
104       this.valueExpression = valueExpression;
105     }
106 
107     /**
108      * Get the map entry key expression.
109      *
110      * @return the key expression
111      */
112     @NonNull
113     public IExpression getKeyExpression() {
114       return keyExpression;
115     }
116 
117     /**
118      * Get the map entry value expression.
119      *
120      * @return the value expression
121      */
122     @NonNull
123     public IExpression getValueExpression() {
124       return valueExpression;
125     }
126 
127     @SuppressWarnings("null")
128     @Override
129     public List<? extends IExpression> getChildren() {
130       return List.of(keyExpression, valueExpression);
131     }
132 
133     @Override
134     protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
135       throw new UnsupportedOperationException("handled by the map constructor");
136     }
137 
138     @Override
139     public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
140       return visitor.visitMapConstructorEntry(this, context);
141     }
142   }
143 }