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