1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.function;
7   
8   import java.util.Objects;
9   
10  import dev.metaschema.core.metapath.StaticContext;
11  import dev.metaschema.core.metapath.StaticMetapathException;
12  import dev.metaschema.core.metapath.item.IItem;
13  import dev.metaschema.core.metapath.type.IItemType;
14  import dev.metaschema.core.metapath.type.ISequenceType;
15  import dev.metaschema.core.metapath.type.Occurrence;
16  import dev.metaschema.core.qname.EQNameFactory;
17  import dev.metaschema.core.qname.IEnhancedQName;
18  import dev.metaschema.core.util.ObjectUtils;
19  import edu.umd.cs.findbugs.annotations.NonNull;
20  
21  /**
22   * Represents a single function argument signature.
23   */
24  public interface IArgument {
25    /**
26     * Create a new argument with the provided name and sequence type.
27     *
28     * @param name
29     *          the argument's name
30     * @param sequenceType
31     *          the argument's sequence type
32     * @return a new argument instance
33     */
34    @NonNull
35    static IArgument of(@NonNull IEnhancedQName name, @NonNull ISequenceType sequenceType) {
36      return new ArgumentImpl(name, sequenceType);
37    }
38  
39    /**
40     * Get the argument's name.
41     *
42     * @return the argument's name
43     */
44    @NonNull
45    IEnhancedQName getName();
46  
47    /**
48     * Get information about the type of sequence supported by the argument.
49     *
50     * @return the sequence information
51     */
52    @NonNull
53    ISequenceType getSequenceType();
54  
55    /**
56     * Get the signature of the argument.
57     *
58     * @return the argument's signature
59     */
60    @NonNull
61    String toSignature();
62  
63    /**
64     * Get a new argument builder.
65     *
66     * @return the new argument builder
67     */
68    @NonNull
69    static Builder builder() {
70      return new Builder();
71    }
72  
73    /**
74     * Resolve an argument name from a prefix.
75     *
76     * @param prefix
77     *          the prefix to resolve
78     * @return the resolved argument name
79     * @throws UnsupportedOperationException
80     *           if a non-empty prefix is provided
81     */
82    @NonNull
83    static String resolveArgumentName(@NonNull String prefix) {
84      if (!"".equals(prefix)) {
85        throw new UnsupportedOperationException("Lexical qualified names are not allowed.");
86      }
87      return "";
88    }
89  
90    /**
91     * Used to create an argument's signature using a builder pattern.
92     */
93    final class Builder {
94      private IEnhancedQName name;
95      @NonNull
96      private IItemType type;
97      private Occurrence occurrence;
98  
99      private Builder() {
100       // construct a new non-initialized builder
101       this.type = IItem.type();
102     }
103 
104     /**
105      * Define the name of the function argument.
106      *
107      * @param name
108      *          the argument's name
109      * @return this builder
110      */
111     @NonNull
112     public Builder name(@NonNull String name) {
113       if (Objects.requireNonNull(name, "name").isBlank()) {
114         throw new IllegalArgumentException("the name must be non-blank");
115       }
116       this.name = EQNameFactory.instance().parseName(name, IArgument::resolveArgumentName);
117       return this;
118     }
119 
120     /**
121      * Define the type of the function argument.
122      * <p>
123      * By default an argument has the type {@link IItem}.
124      *
125      * @param name
126      *          the qualified name of the argument's type
127      * @return this builder
128      */
129     @NonNull
130     public Builder type(@NonNull IEnhancedQName name) {
131       try {
132         this.type = StaticContext.lookupAtomicType(name);
133       } catch (StaticMetapathException ex) {
134         throw new IllegalArgumentException(
135             String.format("No data type with the name '%s'.", name), ex);
136       }
137       return this;
138     }
139 
140     /**
141      * Define the type of the function argument.
142      * <p>
143      * By default an argument has the type {@link IItem}.
144      *
145      * @param type
146      *          the argument's type
147      * @return this builder
148      */
149     @NonNull
150     public Builder type(@NonNull IItemType type) {
151       this.type = type;
152       return this;
153     }
154 
155     /**
156      * Identifies the argument's cardinality as a single, optional item (zero or
157      * one).
158      *
159      * @return this builder
160      */
161     @NonNull
162     public Builder zeroOrOne() {
163       return occurrence(Occurrence.ZERO_OR_ONE);
164     }
165 
166     /**
167      * Identifies the argument's cardinality as a single, required item (one).
168      *
169      * @return this builder
170      */
171     @NonNull
172     public Builder one() {
173       return occurrence(Occurrence.ONE);
174     }
175 
176     /**
177      * Identifies the argument's cardinality as an optional series of items (zero or
178      * more).
179      *
180      * @return this builder
181      */
182     @NonNull
183     public Builder zeroOrMore() {
184       return occurrence(Occurrence.ZERO_OR_MORE);
185     }
186 
187     /**
188      * Identifies the argument's cardinality as a required series of items (one or
189      * more).
190      *
191      * @return this builder
192      */
193     @NonNull
194     public Builder oneOrMore() {
195       return occurrence(Occurrence.ONE_OR_MORE);
196     }
197 
198     @NonNull
199     private Builder occurrence(@NonNull Occurrence occurrence) {
200       Objects.requireNonNull(occurrence, "occurrence");
201       this.occurrence = occurrence;
202       return this;
203     }
204 
205     /**
206      * Builds the argument's signature.
207      *
208      * @return the argument's signature
209      */
210     @NonNull
211     public IArgument build() {
212       return new ArgumentImpl(
213           ObjectUtils.requireNonNull(name, "the argument name must not be null"),
214           ISequenceType.of(type, ObjectUtils.requireNonNull(occurrence, "occurrence")));
215     }
216   }
217 }