1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath.function;
7   
8   import gov.nist.secauto.metaschema.core.metapath.ISequence;
9   import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
10  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
11  
12  import java.util.Objects;
13  
14  import edu.umd.cs.findbugs.annotations.NonNull;
15  
16  /**
17   * Identifies the occurrence of a sequence used a function argument or return
18   * value.
19   */
20  public enum Occurrence {
21    /**
22     * An empty sequence.
23     */
24    ZERO("", true, Occurrence::handleZero),
25    /**
26     * The occurrence indicator {@code "?"}.
27     */
28    ZERO_OR_ONE("?", true, Occurrence::handleZeroOrOne),
29    /**
30     * No occurrence indicator.
31     */
32    ONE("", false, Occurrence::handleOne),
33    /**
34     * The occurrence indicator {@code "*"}.
35     */
36    ZERO_OR_MORE("*", true, Occurrence::handleZeroOrMore),
37    /**
38     * The occurrence indicator {@code "+"}.
39     */
40    ONE_OR_MORE("+", false, Occurrence::handleOneOrMore);
41  
42    @NonNull
43    private final String indicator;
44    private final boolean optional;
45    @NonNull
46    private final ISequenceHandler sequenceHandler;
47  
48    Occurrence(@NonNull String indicator, boolean optional, @NonNull ISequenceHandler sequenceHandler) {
49      Objects.requireNonNull(indicator, "indicator");
50      this.indicator = indicator;
51      this.optional = optional;
52      this.sequenceHandler = sequenceHandler;
53    }
54  
55    /**
56     * Get the occurrence indicator to use in the signature string for the argument.
57     *
58     * @return the occurrence indicator
59     */
60    @NonNull
61    public String getIndicator() {
62      return indicator;
63    }
64  
65    /**
66     * Determine if providing a value is optional based on the occurrence.
67     *
68     * @return {@code true} if providing a value is optional or {@code false} if
69     *         required
70     */
71    public boolean isOptional() {
72      return optional;
73    }
74  
75    /**
76     * Get the handler used to check that a sequence meets the occurrence
77     * requirement.
78     *
79     * @return the handler
80     */
81    @NonNull
82    public ISequenceHandler getSequenceHandler() {
83      return sequenceHandler;
84    }
85  
86    @NonNull
87    private static <T extends IItem> ISequence<T> handleZero(@NonNull ISequence<T> sequence) {
88      int size = sequence.size();
89      if (size != 0) {
90        throw new InvalidTypeMetapathException(
91            null,
92            String.format("an empty sequence expected, but size is '%d'", size));
93      }
94      return ISequence.empty();
95    }
96  
97    @NonNull
98    private static <T extends IItem> ISequence<T> handleOne(@NonNull ISequence<T> sequence) {
99      int size = sequence.size();
100     if (size != 1) {
101       throw new InvalidTypeMetapathException(
102           null,
103           String.format("a sequence of one expected, but size is '%d'", size));
104     }
105 
106     T item = sequence.getFirstItem(true);
107     return item == null ? ISequence.empty() : ISequence.of(item);
108   }
109 
110   @NonNull
111   private static <T extends IItem> ISequence<T> handleZeroOrOne(@NonNull ISequence<T> sequence) {
112     int size = sequence.size();
113     if (size > 1) {
114       throw new InvalidTypeMetapathException(
115           null,
116           String.format("a sequence of zero or one expected, but size is '%d'", size));
117     }
118 
119     T item = sequence.getFirstItem(false);
120     return item == null ? ISequence.empty() : ISequence.of(item);
121   }
122 
123   @NonNull
124   private static <T extends IItem> ISequence<T> handleZeroOrMore(@NonNull ISequence<T> sequence) {
125     return sequence;
126   }
127 
128   @NonNull
129   private static <T extends IItem> ISequence<T> handleOneOrMore(@NonNull ISequence<T> sequence) {
130     int size = sequence.size();
131     if (size < 1) {
132       throw new InvalidTypeMetapathException(
133           null,
134           String.format("a sequence of one or more expected, but size is '%d'", size));
135     }
136     return sequence;
137   }
138 
139   @FunctionalInterface
140   public interface ISequenceHandler {
141     /**
142      * Check the provided sequence matches the occurrence.
143      * <p>
144      * This method may return a new sequence that more efficiently addresses the
145      * occurrence.
146      *
147      * @param <T>
148      *          the sequence item Java type
149      * @param sequence
150      *          the sequence to check occurrence for
151      * @return the sequence
152      * @throws InvalidTypeMetapathException
153      *           if the sequence doesn't match the required occurrence
154      */
155     @NonNull
156     <T extends IItem> ISequence<T> handle(@NonNull ISequence<T> sequence);
157   }
158 }