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