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}