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.ServiceLoader;
9   import java.util.ServiceLoader.Provider;
10  import java.util.stream.Stream;
11  
12  import dev.metaschema.core.metapath.StaticMetapathException;
13  import dev.metaschema.core.qname.IEnhancedQName;
14  import dev.metaschema.core.util.ObjectUtils;
15  import edu.umd.cs.findbugs.annotations.NonNull;
16  import nl.talsmasoftware.lazy4j.Lazy;
17  
18  /**
19   * Supports looking up named Metapath functions that are loaded using the
20   * {@link ServiceLoader} interface.
21   */
22  public final class FunctionService implements IFunctionResolver {
23    private static final Lazy<FunctionService> INSTANCE = Lazy.of(FunctionService::new);
24    @NonNull
25    private final ServiceLoader<IFunctionLibrary> loader;
26    @NonNull
27    private final Lazy<IFunctionLibrary> library;
28  
29    /**
30     * Get the singleton instance of the function service.
31     *
32     * @return the service instance
33     */
34    // FIXME: rename to instance()
35    @NonNull
36    public static FunctionService getInstance() {
37      return ObjectUtils.notNull(INSTANCE.get());
38    }
39  
40    /**
41     * Construct a new function service.
42     */
43    @SuppressWarnings("null")
44    public FunctionService() {
45      this.loader = ServiceLoader.load(IFunctionLibrary.class);
46      ServiceLoader<IFunctionLibrary> loader = getLoader();
47  
48      this.library = Lazy.of(() -> {
49        FunctionLibrary functionLibrary = new FunctionLibrary();
50        loader.stream()
51            .map(Provider<IFunctionLibrary>::get)
52            .flatMap(IFunctionLibrary::stream)
53            .forEachOrdered(function -> functionLibrary.registerFunction(ObjectUtils.notNull(function)));
54        return functionLibrary;
55      });
56    }
57  
58    /**
59     * Get the function service loader instance.
60     *
61     * @return the service loader instance.
62     */
63    @NonNull
64    private ServiceLoader<IFunctionLibrary> getLoader() {
65      return loader;
66    }
67  
68    @NonNull
69    private IFunctionLibrary getLibrary() {
70      return ObjectUtils.notNull(library.get());
71    }
72  
73    /**
74     * Retrieve the collection of function signatures in this library as a stream.
75     *
76     * @return a stream of function signatures
77     */
78    @NonNull
79    public Stream<IFunction> stream() {
80      return getLibrary().stream();
81    }
82  
83    @Override
84    public IFunction getFunction(@NonNull IEnhancedQName name, int arity) {
85      IFunction retval = getLibrary().getFunction(name, arity);
86  
87      if (retval == null) {
88        throw new StaticMetapathException(StaticMetapathException.NO_FUNCTION_MATCH,
89            String.format("unable to find function with name '%s' having arity '%d'", name, arity));
90      }
91      return retval;
92    }
93  }