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.util.ObjectUtils;
10  
11  import java.util.ServiceLoader;
12  import java.util.ServiceLoader.Provider;
13  import java.util.stream.Stream;
14  
15  import javax.xml.namespace.QName;
16  
17  import edu.umd.cs.findbugs.annotations.NonNull;
18  import nl.talsmasoftware.lazy4j.Lazy;
19  
20  public final class FunctionService {
21    private static final Lazy<FunctionService> INSTANCE = Lazy.lazy(() -> new FunctionService());
22    @NonNull
23    private final ServiceLoader<IFunctionLibrary> loader;
24    @NonNull
25    private final Lazy<IFunctionLibrary> library;
26  
27    /**
28     * Get the singleton instance of the function service.
29     *
30     * @return the service instance
31     */
32    public static FunctionService getInstance() {
33      return INSTANCE.get();
34    }
35  
36    /**
37     * Construct a new function service.
38     */
39    @SuppressWarnings("null")
40    public FunctionService() {
41      this.loader = ServiceLoader.load(IFunctionLibrary.class);
42      ServiceLoader<IFunctionLibrary> loader = getLoader();
43  
44      this.library = Lazy.lazy(() -> {
45        FunctionLibrary functionLibrary = new FunctionLibrary();
46        loader.stream()
47            .map(Provider<IFunctionLibrary>::get)
48            .flatMap(IFunctionLibrary::stream)
49            .forEachOrdered(function -> functionLibrary.registerFunction(ObjectUtils.notNull(function)));
50        return functionLibrary;
51      });
52    }
53  
54    /**
55     * Get the function service loader instance.
56     *
57     * @return the service loader instance.
58     */
59    @NonNull
60    private ServiceLoader<IFunctionLibrary> getLoader() {
61      return loader;
62    }
63  
64    @NonNull
65    private IFunctionLibrary getLibrary() {
66      return ObjectUtils.notNull(library.get());
67    }
68  
69    /**
70     * Retrieve the collection of function signatures in this library as a stream.
71     *
72     * @return a stream of function signatures
73     */
74    public Stream<IFunction> stream() {
75      return getLibrary().stream();
76    }
77  
78    /**
79     * Retrieve the function with the provided name that supports the signature of
80     * the provided methods, if such a function exists.
81     *
82     * @param name
83     *          the name of a group of functions
84     * @param arity
85     *          the count of arguments for use in determining an argument signature
86     *          match
87     * @return the matching function or {@code null} if no match exists
88     * @throws StaticMetapathException
89     *           if a matching function was not found
90     */
91    public IFunction getFunction(@NonNull String name, int arity) {
92      IFunction retval;
93      synchronized (this) {
94        retval = getLibrary().getFunction(name, arity);
95      }
96  
97      if (retval == null) {
98        throw new StaticMetapathException(StaticMetapathException.NO_FUNCTION_MATCH,
99            String.format("unable to find function with name '%s' having arity '%d'", name, arity));
100     }
101     return retval;
102   }
103 
104   /**
105    * Retrieve the function with the provided name that supports the signature of
106    * the provided methods, if such a function exists.
107    *
108    * @param name
109    *          the name of a group of functions
110    * @param arity
111    *          the count of arguments for use in determining an argument signature
112    *          match
113    * @return the matching function or {@code null} if no match exists
114    * @throws StaticMetapathException
115    *           if a matching function was not found
116    */
117   public IFunction getFunction(@NonNull QName name, int arity) {
118     IFunction retval;
119     synchronized (this) {
120       retval = getLibrary().getFunction(name, arity);
121     }
122 
123     if (retval == null) {
124       throw new StaticMetapathException(StaticMetapathException.NO_FUNCTION_MATCH,
125           String.format("unable to find function with name '%s' having arity '%d'", name, arity));
126     }
127     return retval;
128   }
129 
130 }