001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.qname;
007
008import java.net.URI;
009import java.util.Map;
010import java.util.Optional;
011import java.util.concurrent.ConcurrentHashMap;
012import java.util.concurrent.atomic.AtomicInteger;
013
014import dev.metaschema.core.util.ObjectUtils;
015import edu.umd.cs.findbugs.annotations.NonNull;
016import nl.talsmasoftware.lazy4j.Lazy;
017
018/**
019 * An integer-based cache of namespaces to reduce the memory footprint of
020 * namespaces used by reusing instances with the same namespace.
021 */
022public final class NamespaceCache {
023  @NonNull
024  private static final Lazy<NamespaceCache> INSTANCE = ObjectUtils.notNull(Lazy.of(NamespaceCache::new));
025
026  private final Map<String, Integer> nsToIndex = new ConcurrentHashMap<>();
027  private final Map<Integer, String> indexToNs = new ConcurrentHashMap<>();
028  private final Map<Integer, URI> indexToNsUri = new ConcurrentHashMap<>();
029  /**
030   * The next available namespace index position.
031   * <p>
032   * This value starts at 1, since the "" no namspace has the zero position.
033   */
034  private final AtomicInteger indexCounter = new AtomicInteger();
035
036  /**
037   * Get the singleton instance.
038   *
039   * @return the singleton instance
040   */
041  @NonNull
042  public static NamespaceCache instance() {
043    return ObjectUtils.notNull(INSTANCE.get());
044  }
045
046  private NamespaceCache() {
047    // claim the "0" position
048    int noNamespaceIndex = indexOf("");
049    assert noNamespaceIndex == 0;
050  }
051
052  /**
053   * Get the index value of the provided namespace.
054   *
055   * @param namespace
056   *          the namespace
057   * @return the index value
058   */
059  public int indexOf(@NonNull String namespace) {
060    return nsToIndex.computeIfAbsent(namespace, key -> {
061      int nextIndex = indexCounter.getAndIncrement();
062      indexToNs.put(nextIndex, namespace);
063      return nextIndex;
064    });
065  }
066
067  /**
068   * Lookup the index value for an existing namespace.
069   *
070   * @param namespace
071   *          the namespace to lookup
072   * @return an optional containing the index value, if it exists
073   */
074  @NonNull
075  public Optional<Integer> get(@NonNull String namespace) {
076    return ObjectUtils.notNull(Optional.ofNullable(nsToIndex.get(namespace)));
077  }
078
079  /**
080   * Lookup the namespace using the index value for an existing namespace.
081   *
082   * @param index
083   *          the index value to lookup
084   * @return an optional containing the namespace, if the index value exists
085   */
086  @NonNull
087  public Optional<String> get(int index) {
088    return ObjectUtils.notNull(Optional.ofNullable(indexToNs.get(index)));
089  }
090
091  /**
092   * Lookup the namespace using the index value for an existing namespace.
093   *
094   * @param index
095   *          the index value to lookup
096   * @return an optional containing the namespace as a URI, if the index value
097   *         exists
098   */
099  @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}