1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.function.library;
7   
8   import java.util.List;
9   
10  import dev.metaschema.core.metapath.DynamicContext;
11  import dev.metaschema.core.metapath.MetapathConstants;
12  import dev.metaschema.core.metapath.function.FunctionUtils;
13  import dev.metaschema.core.metapath.function.IArgument;
14  import dev.metaschema.core.metapath.function.IFunction;
15  import dev.metaschema.core.metapath.function.InvalidArgumentFunctionException;
16  import dev.metaschema.core.metapath.function.UriFunctionException;
17  import dev.metaschema.core.metapath.item.IItem;
18  import dev.metaschema.core.metapath.item.ISequence;
19  import dev.metaschema.core.metapath.item.atomic.IAnyUriItem;
20  import dev.metaschema.core.metapath.item.atomic.IStringItem;
21  import dev.metaschema.core.util.ObjectUtils;
22  import edu.umd.cs.findbugs.annotations.NonNull;
23  import edu.umd.cs.findbugs.annotations.Nullable;
24  
25  /**
26   * Implements the XPath 3.1 <a href=
27   * "https://www.w3.org/TR/xpath-functions-31/#func-resolve-uri">resolve-ur</a>
28   * functions.
29   */
30  public final class FnResolveUri {
31    private static final String NAME = "resolve-uri";
32    @NonNull
33    static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
34        .name(NAME)
35        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
36        .deterministic()
37        .contextDependent()
38        .focusIndependent()
39        .argument(IArgument.builder()
40            .name("relative")
41            .type(IStringItem.type())
42            .zeroOrOne()
43            .build())
44        .returnType(IAnyUriItem.type())
45        .returnZeroOrOne()
46        .functionHandler(FnResolveUri::executeOneArg)
47        .build();
48  
49    @NonNull
50    static final IFunction SIGNATURE_TWO_ARG = IFunction.builder()
51        .name(NAME)
52        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
53        .deterministic()
54        .contextIndependent()
55        .focusIndependent()
56        .argument(IArgument.builder()
57            .name("relative")
58            .type(IStringItem.type())
59            .zeroOrOne()
60            .build())
61        .argument(IArgument.builder()
62            .name("base")
63            .type(IStringItem.type())
64            .one()
65            .build())
66        .returnType(IAnyUriItem.type())
67        .returnZeroOrOne()
68        .functionHandler(FnResolveUri::executeTwoArg)
69        .build();
70  
71    private FnResolveUri() {
72      // disable construction
73    }
74  
75    @SuppressWarnings("unused")
76    @NonNull
77    private static ISequence<IAnyUriItem> executeOneArg(
78        @NonNull IFunction function,
79        @NonNull List<ISequence<?>> arguments,
80        @NonNull DynamicContext dynamicContext,
81        IItem focus) {
82  
83      ISequence<? extends IStringItem> relativeSequence
84          = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0)));
85      if (relativeSequence.isEmpty()) {
86        return ISequence.empty();
87      }
88  
89      IStringItem relativeString = relativeSequence.getFirstItem(true);
90      IAnyUriItem resolvedUri = null;
91      if (relativeString != null) {
92        resolvedUri = fnResolveUri(relativeString, null, dynamicContext);
93      }
94      return ISequence.of(resolvedUri);
95    }
96  
97    /**
98     * Implements the two argument version of the XPath 3.1 function <a href=
99     * "https://www.w3.org/TR/xpath-functions-31/#func-resolve-uri">resolve-uri</a>.
100    *
101    * @param function
102    *          the function definition
103    * @param arguments
104    *          a list of sequence arguments with an expected size of 2
105    * @param dynamicContext
106    *          the evaluation context
107    * @param focus
108    *          the current focus item
109    * @return a sequence containing the resolved URI or and empty sequence if
110    *         either the base or relative URI is {@code null}
111    */
112   @NonNull
113   private static ISequence<IAnyUriItem> executeTwoArg(
114       @NonNull IFunction function,
115       @NonNull List<ISequence<?>> arguments,
116       @NonNull DynamicContext dynamicContext,
117       IItem focus) {
118 
119     /* there will always be two arguments */
120     assert arguments.size() == 2;
121 
122     ISequence<? extends IStringItem> relativeSequence = FunctionUtils.asType(
123         ObjectUtils.requireNonNull(arguments.get(0)));
124     if (relativeSequence.isEmpty()) {
125       return ISequence.empty();
126     }
127 
128     ISequence<? extends IStringItem> baseSequence = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1)));
129     IStringItem baseString = baseSequence.getFirstItem(true);
130 
131     if (baseString == null) {
132       throw new InvalidArgumentFunctionException(
133           InvalidArgumentFunctionException.INVALID_ARGUMENT_TO_RESOLVE_URI,
134           "Invalid argument to fn:resolve-uri().");
135     }
136     IAnyUriItem baseUri = IAnyUriItem.cast(baseString);
137 
138     IStringItem relativeString = relativeSequence.getFirstItem(true);
139 
140     IAnyUriItem resolvedUri = null;
141     if (relativeString != null) {
142       resolvedUri = fnResolveUri(relativeString, baseUri, dynamicContext);
143     }
144     return ISequence.of(resolvedUri);
145   }
146 
147   /**
148    * Resolve the {@code relative} URI against the provided {@code base} URI.
149    *
150    * @param relative
151    *          the relative URI to resolve
152    * @param base
153    *          the base URI to resolve against
154    * @param dynamicContext
155    *          the evaluation context used to get the static base URI if needed
156    * @return the resolved URI or {@code null} if the {@code relative} URI in
157    *         {@code null}
158    */
159   @Nullable
160   public static IAnyUriItem fnResolveUri(
161       @NonNull IStringItem relative,
162       @Nullable IAnyUriItem base,
163       @NonNull DynamicContext dynamicContext) {
164     return fnResolveUri(IAnyUriItem.cast(relative), base, dynamicContext);
165   }
166 
167   /**
168    * Resolve the {@code relative} URI against the provided {@code base} URI.
169    *
170    * @param relative
171    *          the relative URI to resolve
172    * @param base
173    *          the base URI to resolve against
174    * @param dynamicContext
175    *          the evaluation context used to get the static base URI if needed
176    * @return the resolved URI or {@code null} if the {@code relative} URI in
177    *         {@code null}
178    * @throws UriFunctionException
179    *           if the base URI is not configured in the dynamic context
180    */
181   @NonNull
182   public static IAnyUriItem fnResolveUri(
183       @NonNull IAnyUriItem relative,
184       @Nullable IAnyUriItem base,
185       @NonNull DynamicContext dynamicContext) {
186 
187     IAnyUriItem baseUri = base;
188     if (baseUri == null) {
189       baseUri = FnStaticBaseUri.fnStaticBaseUri(dynamicContext);
190       if (baseUri == null) {
191         throw new UriFunctionException(UriFunctionException.BASE_URI_NOT_DEFINED_IN_STATIC_CONTEXT,
192             "The base-uri is not defined in the static context");
193       }
194     }
195 
196     return baseUri.resolve(relative);
197   }
198 }