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.of(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    public int indexOf(@NonNull String namespace) {
61      return nsToIndex.computeIfAbsent(namespace, key -> {
62        int nextIndex = indexCounter.getAndIncrement();
63        indexToNs.put(nextIndex, namespace);
64        return nextIndex;
65      });
66    }
67  
68    /**
69     * Lookup the index value for an existing namespace.
70     *
71     * @param namespace
72     *          the namespace to lookup
73     * @return an optional containing the index value, if it exists
74     */
75    @NonNull
76    public Optional<Integer> get(@NonNull String namespace) {
77      return ObjectUtils.notNull(Optional.ofNullable(nsToIndex.get(namespace)));
78    }
79  
80    /**
81     * Lookup the namespace using the index value for an existing namespace.
82     *
83     * @param index
84     *          the index value to lookup
85     * @return an optional containing the namespace, if the index value exists
86     */
87    @NonNull
88    public Optional<String> get(int index) {
89      return ObjectUtils.notNull(Optional.ofNullable(indexToNs.get(index)));
90    }
91  
92    /**
93     * Lookup the namespace using the index value for an existing namespace.
94     *
95     * @param index
96     *          the index value to lookup
97     * @return an optional containing the namespace as a URI, if the index value
98     *         exists
99     */
100   @NonNull
101   public Optional<URI> getAsURI(int index) {
102     return ObjectUtils.notNull(Optional.ofNullable(indexToNsUri.computeIfAbsent(index, key -> {
103       Optional<String> namespace = get(key);
104       URI nsUri = null;
105       if (namespace.isPresent()) {
106         nsUri = URI.create(namespace.get());
107         indexToNsUri.put(key, nsUri);
108       }
109       return nsUri;
110     })));
111   }
112 }