1
2
3
4
5
6 package dev.metaschema.core.qname;
7
8 import java.net.URI;
9 import java.util.Comparator;
10 import java.util.Map;
11 import java.util.Objects;
12 import java.util.Optional;
13 import java.util.concurrent.ConcurrentHashMap;
14 import java.util.concurrent.atomic.AtomicInteger;
15
16 import dev.metaschema.core.util.ObjectUtils;
17 import edu.umd.cs.findbugs.annotations.NonNull;
18 import edu.umd.cs.findbugs.annotations.Nullable;
19 import nl.talsmasoftware.lazy4j.Lazy;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public final class QNameCache {
38
39 private static final Comparator<IEnhancedQName> COMPARATOR
40 = Comparator.comparingInt(IEnhancedQName::getIndexPosition);
41
42 @NonNull
43 private static final Lazy<QNameCache> INSTANCE = ObjectUtils.notNull(Lazy.of(QNameCache::new));
44
45 @NonNull
46 private final NamespaceCache namespaceCache;
47
48 private final Map<Integer, QNameRecord> indexToQName = new ConcurrentHashMap<>();
49 private final Map<Integer, Map<String, QNameRecord>> nsIndexToLocalNameToIndex = new ConcurrentHashMap<>();
50
51
52
53
54 private final AtomicInteger indexCounter = new AtomicInteger();
55
56
57
58
59
60
61 @NonNull
62 public static QNameCache instance() {
63 return ObjectUtils.notNull(INSTANCE.get());
64 }
65
66 private QNameCache() {
67
68 this(NamespaceCache.instance());
69 }
70
71 private QNameCache(@NonNull NamespaceCache nsCache) {
72 this.namespaceCache = nsCache;
73 }
74
75 @NonNull
76 private NamespaceCache getNamespaceCache() {
77 return namespaceCache;
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92 @NonNull
93 public IEnhancedQName cachedQNameFor(@NonNull String namespace, @NonNull String name) {
94 int namespacePosition = namespaceCache.indexOf(namespace);
95
96 Map<String, QNameRecord> namespaceNames = nsIndexToLocalNameToIndex
97 .computeIfAbsent(namespacePosition, key -> new ConcurrentHashMap<>());
98
99 return ObjectUtils.notNull(namespaceNames.computeIfAbsent(name, key -> {
100 assert key != null;
101 QNameRecord record = new QNameRecord(namespacePosition, namespace, key);
102 indexToQName.put(record.getIndexPosition(), record);
103 return record;
104 }));
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118 @NonNull
119 Optional<IEnhancedQName> get(@NonNull String namespace, @NonNull String name) {
120 Optional<Integer> nsPosition = namespaceCache.get(namespace);
121 if (!nsPosition.isPresent()) {
122 throw new IllegalArgumentException(
123 String.format("The namespace '%s' is not recognized.", namespace));
124 }
125
126 Map<String, QNameRecord> namespaceNames = nsIndexToLocalNameToIndex.get(nsPosition.get());
127 return ObjectUtils.notNull(Optional.ofNullable(
128 namespaceNames == null
129 ? null
130 : namespaceNames.get(name)));
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 @Nullable
147 public IEnhancedQName get(int index) {
148 return indexToQName.get(index);
149 }
150
151 private final class QNameRecord implements IEnhancedQName {
152 private final int qnameIndexPosition;
153 private final int namespaceIndexPosition;
154 @NonNull
155 private final String namespace;
156 @NonNull
157 private final String localName;
158
159 public QNameRecord(
160 int namespaceIndexPosition,
161 @NonNull String namespace,
162 @NonNull String localName) {
163 this.qnameIndexPosition = indexCounter.getAndIncrement();
164 this.namespaceIndexPosition = namespaceIndexPosition;
165 this.namespace = namespace;
166 this.localName = localName;
167 }
168
169 @Override
170 public int getIndexPosition() {
171 return qnameIndexPosition;
172 }
173
174 @Override
175 public URI getNamespaceAsUri() {
176 return ObjectUtils.notNull(getNamespaceCache().getAsURI(namespaceIndexPosition).get());
177 }
178
179 @Override
180 public String getNamespace() {
181 return namespace;
182 }
183
184 @Override
185 public String getLocalName() {
186 return localName;
187 }
188
189 @Override
190 public int hashCode() {
191 return Objects.hashCode(qnameIndexPosition);
192 }
193
194 @Override
195 public boolean equals(Object obj) {
196 if (this == obj) {
197 return true;
198 }
199 if (obj == null || getClass() != obj.getClass()) {
200 return false;
201 }
202 QNameRecord other = (QNameRecord) obj;
203 return Objects.equals(qnameIndexPosition, other.getIndexPosition());
204 }
205
206 @Override
207 public int compareTo(IEnhancedQName other) {
208 return COMPARATOR.compare(this, other);
209 }
210
211 @Override
212 public String toString() {
213 return toEQName();
214 }
215 }
216
217 }