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