1
2
3
4
5
6 package dev.metaschema.schemagen;
7
8 import java.util.Collection;
9 import java.util.HashSet;
10 import java.util.LinkedHashMap;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.concurrent.atomic.AtomicBoolean;
14
15 import dev.metaschema.core.model.IAssemblyDefinition;
16 import dev.metaschema.core.model.IAssemblyInstance;
17 import dev.metaschema.core.model.IChoiceInstance;
18 import dev.metaschema.core.model.IDefinition;
19 import dev.metaschema.core.model.IFieldDefinition;
20 import dev.metaschema.core.model.IFieldInstance;
21 import dev.metaschema.core.model.IFlagDefinition;
22 import dev.metaschema.core.model.IFlagInstance;
23 import dev.metaschema.core.model.IModule;
24 import dev.metaschema.core.model.INamedInstance;
25 import dev.metaschema.core.model.INamedModelInstance;
26 import dev.metaschema.core.model.INamedModelInstanceGrouped;
27 import dev.metaschema.core.model.ModelWalker;
28 import dev.metaschema.core.util.ObjectUtils;
29 import edu.umd.cs.findbugs.annotations.NonNull;
30
31
32
33
34
35
36
37
38 public class ModuleIndex {
39
40 @SuppressWarnings("PMD.UseConcurrentHashMap")
41 private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>();
42
43
44
45
46
47
48
49
50
51
52
53 @NonNull
54 public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) {
55 Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions();
56 ModuleIndex index = new ModuleIndex();
57 if (!definitions.isEmpty()) {
58 IndexVisitor visitor = new IndexVisitor(index, inlineStrategy);
59 for (IAssemblyDefinition definition : definitions) {
60 assert definition != null;
61
62
63
64
65
66 visitor.walk(ObjectUtils.requireNonNull(definition));
67 }
68 }
69 return index;
70 }
71
72
73
74
75
76
77
78
79
80 public boolean hasEntry(@NonNull IDefinition definition) {
81 return index.containsKey(definition);
82 }
83
84
85
86
87
88
89
90
91
92
93
94 @NonNull
95 public DefinitionEntry getEntry(@NonNull IDefinition definition) {
96 return ObjectUtils.notNull(index.computeIfAbsent(
97 definition,
98 k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k))));
99 }
100
101
102
103
104
105
106
107 @NonNull
108 public Collection<DefinitionEntry> getDefinitions() {
109 return ObjectUtils.notNull(index.values());
110 }
111
112 private static class IndexVisitor
113 extends ModelWalker<ModuleIndex> {
114 @NonNull
115 private final IInlineStrategy inlineStrategy;
116 @NonNull
117 private final ModuleIndex index;
118
119 public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) {
120 this.index = index;
121 this.inlineStrategy = inlineStrategy;
122 }
123
124 @Override
125 protected ModuleIndex getDefaultData() {
126 return index;
127 }
128
129 @Override
130 protected boolean visit(IFlagInstance instance, ModuleIndex index) {
131 handleInstance(instance);
132 return true;
133 }
134
135 @Override
136 protected boolean visit(IFieldInstance instance, ModuleIndex index) {
137 handleInstance(instance);
138 return true;
139 }
140
141 @Override
142 protected boolean visit(IAssemblyInstance instance, ModuleIndex index) {
143 handleInstance(instance);
144 return true;
145 }
146
147 @Override
148 protected void visit(IFlagDefinition def, ModuleIndex data) {
149 handleDefinition(def);
150 }
151
152
153
154
155
156
157
158 @Override
159 protected boolean visit(IFieldDefinition def, ModuleIndex data) {
160 return handleDefinition(def);
161 }
162
163 @Override
164 protected boolean visit(IAssemblyDefinition def, ModuleIndex data) {
165 return handleDefinition(def);
166 }
167
168 private boolean handleDefinition(@NonNull IDefinition definition) {
169 DefinitionEntry entry = getDefaultData().getEntry(definition);
170 boolean visited = entry.isVisited();
171 if (!visited) {
172 entry.markVisited();
173
174 if (inlineStrategy.isInline(definition, index)) {
175 entry.markInline();
176 }
177 }
178 return !visited;
179 }
180
181
182
183
184
185
186
187 @NonNull
188 private DefinitionEntry handleInstance(INamedInstance instance) {
189 IDefinition definition = instance.getDefinition();
190
191
192 DefinitionEntry entry = getDefaultData().getEntry(definition);
193 entry.addReference(instance);
194
195 if (isChoice(instance)) {
196 entry.markUsedAsChoice();
197 }
198
199 if (isChoiceSibling(instance)) {
200 entry.markAsChoiceSibling();
201 }
202 return entry;
203 }
204
205 private static boolean isChoice(@NonNull INamedInstance instance) {
206 return instance.getParentContainer() instanceof IChoiceInstance;
207 }
208
209 private static boolean isChoiceSibling(@NonNull INamedInstance instance) {
210 IDefinition containingDefinition = instance.getContainingDefinition();
211 return containingDefinition instanceof IAssemblyDefinition
212 && !((IAssemblyDefinition) containingDefinition).getChoiceInstances().isEmpty();
213 }
214 }
215
216
217
218
219
220
221
222 public static class DefinitionEntry {
223 @NonNull
224 private final IDefinition definition;
225 private final Set<INamedInstance> references = new HashSet<>();
226 private final AtomicBoolean inline = new AtomicBoolean();
227 private final AtomicBoolean visited = new AtomicBoolean();
228 private final AtomicBoolean usedAsChoice = new AtomicBoolean();
229 private final AtomicBoolean choiceSibling = new AtomicBoolean();
230
231
232
233
234
235
236
237 public DefinitionEntry(@NonNull IDefinition definition) {
238 this.definition = definition;
239 }
240
241
242
243
244
245
246 @NonNull
247 public IDefinition getDefinition() {
248 return definition;
249 }
250
251
252
253
254
255
256
257 public boolean isRoot() {
258 return definition instanceof IAssemblyDefinition
259 && ((IAssemblyDefinition) definition).isRoot();
260 }
261
262
263
264
265
266
267
268
269 public boolean isReferenced() {
270 return !references.isEmpty()
271 || isRoot();
272 }
273
274
275
276
277
278
279 public Set<INamedInstance> getReferences() {
280 return references;
281 }
282
283
284
285
286
287
288
289
290
291 public boolean addReference(@NonNull INamedInstance reference) {
292 return references.add(reference);
293 }
294
295
296
297
298 public void markVisited() {
299 visited.compareAndSet(false, true);
300 }
301
302
303
304
305
306
307 public boolean isVisited() {
308 return visited.get();
309 }
310
311
312
313
314 public void markInline() {
315 inline.compareAndSet(false, true);
316 }
317
318
319
320
321
322
323 public boolean isInline() {
324 return inline.get();
325 }
326
327
328
329
330 public void markUsedAsChoice() {
331 usedAsChoice.compareAndSet(false, true);
332 }
333
334
335
336
337
338
339
340 public boolean isUsedAsChoice() {
341 return usedAsChoice.get();
342 }
343
344
345
346
347 public void markAsChoiceSibling() {
348 choiceSibling.compareAndSet(false, true);
349 }
350
351
352
353
354
355
356
357 public boolean isChoiceSibling() {
358 return choiceSibling.get();
359 }
360
361
362
363
364
365
366 public boolean isUsedAsJsonKey() {
367 return references.stream()
368 .anyMatch(ref -> ref instanceof INamedModelInstance
369 && ((INamedModelInstance) ref).hasJsonKey());
370 }
371
372
373
374
375
376
377
378
379 public boolean isUsedWithoutJsonKey() {
380 return definition instanceof IFlagDefinition
381 || references.isEmpty()
382 || references.stream()
383 .anyMatch(ref -> ref instanceof INamedModelInstance
384 && !((INamedModelInstance) ref).hasJsonKey());
385 }
386
387
388
389
390
391
392
393 public boolean isChoiceGroupMember() {
394 return references.stream()
395 .anyMatch(INamedModelInstanceGrouped.class::isInstance);
396 }
397 }
398 }