1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.qname;
7   
8   import gov.nist.secauto.metaschema.core.util.ObjectUtils;
9   
10  import java.net.URI;
11  import java.util.Map;
12  import java.util.Optional;
13  import java.util.concurrent.ConcurrentHashMap;
14  import java.util.concurrent.atomic.AtomicInteger;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import nl.talsmasoftware.lazy4j.Lazy;
18  
19  /**
20   * An integer-based cache of namespaces to reduce the memory footprint of
21   * namespaces used by reusing instances with the same namespace.
22   */
23  public final class NamespaceCache {
24    @NonNull
25    private static final Lazy<NamespaceCache> INSTANCE = ObjectUtils.notNull(Lazy.lazy(NamespaceCache::new));
26  
27    private final Map<String, Integer> nsToIndex = new ConcurrentHashMap<>();
28    private final Map<Integer, String> indexToNs = new ConcurrentHashMap<>();
29    private final Map<Integer, URI> indexToNsUri = new ConcurrentHashMap<>();
30    /**
31     * The next available namespace index position.
32     * <p>
33     * This value starts at 1, since the "" no namspace has the zero position.
34     */
35    private final AtomicInteger indexCounter = new AtomicInteger();
36  
37    /**
38     * Get the singleton instance.
39     *
40     * @return the singleton instance
41     */
42    @NonNull
43    public static NamespaceCache instance() {
44      return ObjectUtils.notNull(INSTANCE.get());
45    }
46  
47    private NamespaceCache() {
48      // claim the "0" position
49      int noNamespaceIndex = indexOf("");
50      assert noNamespaceIndex == 0;
51    }
52  
53    /**
54     * Get the index value of the provided namespace.
55     *
56     * @param namespace
57     *          the namespace
58     * @return the index value
59     */
60    @SuppressWarnings("PMD.ShortMethodName")
61    public int indexOf(@NonNull String namespace) {
62      return nsToIndex.computeIfAbsent(namespace, key -> {
63        int nextIndex = indexCounter.getAndIncrement();
64        indexToNs.put(nextIndex, namespace);
65        return nextIndex;
66      });
67    }
68  
69    /**
70     * Lookup the index value for an existing namespace.
71     *
72     * @param namespace
73     *          the namespace to lookup
74     * @return an optional containing the index value, if it exists
75     */
76    @NonNull
77    public Optional<Integer> get(@NonNull String namespace) {
78      return ObjectUtils.notNull(Optional.ofNullable(nsToIndex.get(namespace)));
79    }
80  
81    /**
82     * Lookup the namespace using the index value for an existing namespace.
83     *
84     * @param index
85     *          the index value to lookup
86     * @return an optional containing the namespace, if the index value exists
87     */
88    @NonNull
89    public Optional<String> get(int index) {
90      return ObjectUtils.notNull(Optional.ofNullable(indexToNs.get(index)));
91    }
92  
93    /**
94     * Lookup the namespace using the index value for an existing namespace.
95     *
96     * @param index
97     *          the index value to lookup
98     * @return an optional containing the namespace as a URI, if the index value
99     *         exists
100    */
101   @NonNull
102   public Optional<URI> getAsURI(int index) {
103     return ObjectUtils.notNull(Optional.ofNullable(indexToNsUri.computeIfAbsent(index, key -> {
104       Optional<String> namespace = get(key);
105       URI nsUri = null;
106       if (namespace.isPresent()) {
107         nsUri = URI.create(namespace.get());
108         indexToNsUri.put(key, nsUri);
109       }
110       return nsUri;
111     })));
112   }
113 }