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.util.ObjectUtils;
9   
10  import java.util.HashMap;
11  import java.util.Map;
12  import java.util.stream.Stream;
13  
14  import javax.xml.namespace.QName;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import edu.umd.cs.findbugs.annotations.Nullable;
18  
19  public class FunctionLibrary implements IFunctionLibrary {
20  
21    @NonNull
22    private final Map<QName, NamedFunctionSet> libraryByQName = new HashMap<>(); // NOPMD - intentional
23    @NonNull
24    private final Map<String, NamedFunctionSet> libraryByName = new HashMap<>(); // NOPMD - intentional
25  
26    /**
27     * Register the provided function signature.
28     *
29     * @param function
30     *          the function signature to register
31     * @throws IllegalArgumentException
32     *           if the provided function has the same arity as a previously
33     *           registered function with the same name
34     */
35    public final void registerFunction(@NonNull IFunction function) {
36      registerFunctionByQName(function);
37      registerFunctionByName(function);
38    }
39  
40    private void registerFunctionByQName(@NonNull IFunction function) {
41      QName qname = function.getQName();
42      IFunction duplicate;
43      synchronized (this) {
44        NamedFunctionSet functions = libraryByQName.get(qname);
45        if (functions == null) {
46          functions = new NamedFunctionSet();
47          libraryByQName.put(qname, functions);
48        }
49        duplicate = functions.addFunction(function);
50      }
51      if (duplicate != null) {
52        throw new IllegalArgumentException(String.format("Duplicate functions with same arity: %s shadows %s",
53            duplicate.toSignature(), function.toSignature()));
54      }
55    }
56  
57    private void registerFunctionByName(@NonNull IFunction function) {
58      String name = function.getName();
59      synchronized (this) {
60        NamedFunctionSet functions = libraryByName.get(name);
61        if (functions == null) {
62          functions = new NamedFunctionSet();
63          libraryByName.put(name, functions);
64        }
65        // replace duplicates
66        functions.addFunction(function);
67      }
68    }
69  
70    @Override
71    public Stream<IFunction> stream() {
72      synchronized (this) {
73        return ObjectUtils.notNull(libraryByQName.values().stream().flatMap(NamedFunctionSet::getFunctionsAsStream));
74      }
75    }
76  
77    @Override
78    public IFunction getFunction(@NonNull String name, int arity) {
79      IFunction retval = null;
80      synchronized (this) {
81        NamedFunctionSet functions = libraryByName.get(name);
82        if (functions != null) {
83          retval = functions.getFunctionWithArity(arity);
84        }
85      }
86      return retval;
87    }
88  
89    @Override
90    public IFunction getFunction(@NonNull QName name, int arity) {
91      IFunction retval = null;
92      synchronized (this) {
93        NamedFunctionSet functions = libraryByQName.get(name);
94        if (functions != null) {
95          retval = functions.getFunctionWithArity(arity);
96        }
97      }
98      return retval;
99    }
100 
101   private static class NamedFunctionSet {
102     private final Map<Integer, IFunction> arityToFunctionMap;
103     private IFunction unboundedArity;
104 
105     public NamedFunctionSet() {
106       this.arityToFunctionMap = new HashMap<>();
107     }
108 
109     @SuppressWarnings("null")
110     @NonNull
111     public Stream<IFunction> getFunctionsAsStream() {
112       return arityToFunctionMap.values().stream();
113     }
114 
115     @Nullable
116     public IFunction getFunctionWithArity(int arity) {
117       IFunction retval = arityToFunctionMap.get(arity);
118       if (retval == null && unboundedArity != null && unboundedArity.arity() < arity) {
119         retval = unboundedArity;
120       }
121       return retval;
122     }
123 
124     @Nullable
125     public IFunction addFunction(@NonNull IFunction function) {
126       IFunction old = arityToFunctionMap.put(function.arity(), function);
127       if (function.isArityUnbounded()) {
128         unboundedArity = function;
129       }
130       return old;
131     }
132   }
133 }