1
2
3
4
5
6 package dev.metaschema.databind.codegen.config;
7
8 import org.apache.logging.log4j.LogManager;
9 import org.apache.logging.log4j.Logger;
10
11 import java.io.File;
12 import java.io.IOException;
13 import java.net.MalformedURLException;
14 import java.net.URI;
15 import java.net.URISyntaxException;
16 import java.net.URL;
17 import java.nio.file.Path;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.function.Function;
24
25 import dev.metaschema.core.model.IAssemblyDefinition;
26 import dev.metaschema.core.model.IFieldDefinition;
27 import dev.metaschema.core.model.IModelDefinition;
28 import dev.metaschema.core.model.IModule;
29 import dev.metaschema.core.model.INamedInstance;
30 import dev.metaschema.core.util.CollectionUtil;
31 import dev.metaschema.core.util.ObjectUtils;
32 import dev.metaschema.databind.IBindingContext;
33 import dev.metaschema.databind.codegen.ClassUtils;
34 import dev.metaschema.databind.config.binding.MetaschemaBindings;
35 import dev.metaschema.databind.io.BindingException;
36 import dev.metaschema.databind.io.Format;
37 import dev.metaschema.databind.io.IDeserializer;
38 import edu.umd.cs.findbugs.annotations.NonNull;
39 import edu.umd.cs.findbugs.annotations.Nullable;
40
41
42
43
44
45
46
47
48
49 public class DefaultBindingConfiguration implements IBindingConfiguration {
50 private static final Logger LOGGER = LogManager.getLogger(DefaultBindingConfiguration.class);
51
52 private final Map<String, String> namespaceToPackageNameMap = new ConcurrentHashMap<>();
53
54 private final Map<String, MetaschemaBindingConfiguration> moduleUrlToMetaschemaBindingConfigurationMap
55 = new ConcurrentHashMap<>();
56
57 @Override
58 public String getPackageNameForModule(IModule module) {
59 URI namespace = module.getXmlNamespace();
60 return getPackageNameForNamespace(ObjectUtils.notNull(namespace.toASCIIString()));
61 }
62
63
64
65
66
67
68
69
70
71
72
73
74
75 @Override
76 @Nullable
77 public IDefinitionBindingConfiguration getBindingConfigurationForDefinition(
78 @NonNull IModelDefinition definition) {
79 String moduleUri = ObjectUtils.notNull(definition.getContainingModule().getLocation().toASCIIString());
80 String definitionName = definition.getName();
81
82 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri);
83
84 IDefinitionBindingConfiguration retval = null;
85 if (metaschemaConfig != null) {
86 switch (definition.getModelType()) {
87 case ASSEMBLY:
88
89 retval = metaschemaConfig.getAssemblyDefinitionBindingConfig(definitionName);
90
91 if (retval == null && definition.isInline()) {
92 String path = computeDefinitionPath(definition);
93 retval = metaschemaConfig.getAssemblyDefinitionBindingConfig(path);
94 }
95 break;
96 case FIELD:
97
98 retval = metaschemaConfig.getFieldDefinitionBindingConfig(definitionName);
99
100 if (retval == null && definition.isInline()) {
101 String path = computeDefinitionPath(definition);
102 retval = metaschemaConfig.getFieldDefinitionBindingConfig(path);
103 }
104 break;
105 default:
106 throw new UnsupportedOperationException(
107 String.format("Unsupported definition type '%s'", definition.getModelType()));
108 }
109 }
110 return retval;
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125 @NonNull
126 private static String computeDefinitionPath(@NonNull IModelDefinition definition) {
127 StringBuilder path = new StringBuilder();
128 IModelDefinition current = definition;
129
130 while (current.isInline()) {
131 if (path.length() > 0) {
132 path.insert(0, "/");
133 }
134 path.insert(0, current.getName());
135
136
137 INamedInstance inlineInstance = current.getInlineInstance();
138 if (inlineInstance != null) {
139 current = inlineInstance.getContainingDefinition();
140 } else {
141 break;
142 }
143 }
144
145 return ObjectUtils.notNull(path.toString());
146 }
147
148 @Override
149 public String getQualifiedBaseClassName(IModelDefinition definition) {
150 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition);
151 return config == null
152 ? null
153 : config.getQualifiedBaseClassName();
154 }
155
156 @Override
157 public String getClassName(IModelDefinition definition) {
158 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition);
159
160 String retval = null;
161 if (config != null) {
162 retval = config.getClassName();
163 }
164
165 if (retval == null) {
166 retval = ClassUtils.toClassName(definition.getName());
167 }
168 return retval;
169 }
170
171 @NonNull
172 @Override
173 public String getClassName(@NonNull IModule module) {
174
175 return ClassUtils.toClassName(module.getShortName() + "Module");
176 }
177
178 @Override
179 public List<String> getQualifiedSuperinterfaceClassNames(IModelDefinition definition) {
180 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition);
181 return config == null
182 ? CollectionUtil.emptyList()
183 : config.getInterfacesToImplement();
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197 @Nullable
198 public IPropertyBindingConfiguration getPropertyBindingConfiguration(
199 @NonNull IModelDefinition definition,
200 @NonNull String propertyName) {
201 String moduleUri = ObjectUtils.notNull(definition.getContainingModule().getLocation().toASCIIString());
202 String definitionName = definition.getName();
203
204 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri);
205 if (metaschemaConfig == null) {
206 return null;
207 }
208
209 return metaschemaConfig.getPropertyBindingConfig(definitionName, propertyName);
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224 public void addModelBindingConfig(String namespace, String packageName) {
225 if (namespaceToPackageNameMap.containsKey(namespace)) {
226 String oldPackageName = namespaceToPackageNameMap.get(namespace);
227 if (!oldPackageName.equals(packageName)) {
228 throw new IllegalStateException(
229 String.format("Attempt to redefine existing package name '%s' to '%s' for namespace '%s'",
230 oldPackageName,
231 packageName,
232 namespace));
233 }
234 } else {
235 namespaceToPackageNameMap.put(namespace, packageName);
236 }
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250 @NonNull
251 protected String getPackageNameForNamespace(@NonNull String namespace) {
252 String packageName = namespaceToPackageNameMap.get(namespace);
253 if (packageName == null) {
254 packageName = ClassUtils.toPackageName(namespace);
255 }
256 return packageName;
257 }
258
259
260
261
262
263
264
265
266
267 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull IModule module) {
268 String moduleUri = ObjectUtils.notNull(module.getLocation().toString());
269 return getMetaschemaBindingConfiguration(moduleUri);
270
271 }
272
273
274
275
276
277
278
279
280
281
282 @Nullable
283 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull String moduleUri) {
284 return moduleUrlToMetaschemaBindingConfigurationMap.get(moduleUri);
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298 public MetaschemaBindingConfiguration addMetaschemaBindingConfiguration(
299 @NonNull String moduleUri,
300 @NonNull MetaschemaBindingConfiguration config) {
301 Objects.requireNonNull(moduleUri, "moduleUri");
302 Objects.requireNonNull(config, "config");
303 return moduleUrlToMetaschemaBindingConfigurationMap.put(moduleUri, config);
304 }
305
306
307
308
309
310
311
312
313
314
315
316 public void load(Path file) throws IOException, BindingException {
317 URL resource = ObjectUtils.notNull(file.toAbsolutePath().normalize().toUri().toURL());
318 load(resource);
319 }
320
321
322
323
324
325
326
327
328
329
330
331 public void load(File file) throws IOException, BindingException {
332 load(file.toPath());
333 }
334
335
336
337
338
339
340
341
342
343
344
345 public void load(@NonNull URL resource) throws IOException, BindingException {
346 IBindingContext context = IBindingContext.newInstance();
347 IDeserializer<MetaschemaBindings> deserializer = context.newDeserializer(Format.XML, MetaschemaBindings.class);
348
349 MetaschemaBindings bindings;
350 try {
351 bindings = deserializer.deserialize(resource);
352 } catch (IOException | URISyntaxException ex) {
353 throw new IOException("Failed to parse binding configuration: " + resource, ex);
354 }
355
356 List<MetaschemaBindings.ModelBinding> modelBindings = bindings.getModelBindings();
357 for (MetaschemaBindings.ModelBinding model : modelBindings) {
358 processModelBindingConfig(model);
359 }
360
361 List<MetaschemaBindings.MetaschemaBinding> metaschemaBindings = bindings.getMetaschemaBindings();
362 for (MetaschemaBindings.MetaschemaBinding metaschema : metaschemaBindings) {
363 try {
364 processMetaschemaBindingConfig(resource, metaschema);
365 } catch (MalformedURLException | URISyntaxException ex) {
366 throw new IOException(ex);
367 }
368 }
369 }
370
371 private void processModelBindingConfig(MetaschemaBindings.ModelBinding model) {
372 String namespace = model.getNamespace().toString();
373
374 MetaschemaBindings.ModelBinding.Java java = model.getJava();
375 if (java != null) {
376 String packageName = java.getUsePackageName();
377 if (packageName != null) {
378 addModelBindingConfig(namespace, packageName);
379 }
380 }
381 }
382
383 private void processMetaschemaBindingConfig(URL configResource, MetaschemaBindings.MetaschemaBinding metaschema)
384 throws MalformedURLException, URISyntaxException, BindingException {
385 String href = metaschema.getHref().toString();
386 URL moduleUrl = new URL(configResource, href);
387 String moduleUri = ObjectUtils.notNull(moduleUrl.toURI().normalize().toString());
388
389 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri);
390 if (metaschemaConfig == null) {
391 metaschemaConfig = new MetaschemaBindingConfiguration();
392 addMetaschemaBindingConfiguration(moduleUri, metaschemaConfig);
393 }
394
395 List<MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding> assemblyBindings
396 = metaschema.getDefineAssemblyBindings();
397 for (MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding assemblyBinding : assemblyBindings) {
398 String name = assemblyBinding.getName();
399 String target = assemblyBinding.getTarget();
400
401
402 String lookupKey = name != null ? name : target;
403 if (lookupKey != null) {
404 IDefinitionBindingConfiguration config = metaschemaConfig.getAssemblyDefinitionBindingConfig(lookupKey);
405 config = processDefinitionBindingConfiguration(config, assemblyBinding.getJava());
406 metaschemaConfig.addAssemblyDefinitionBindingConfig(lookupKey, config);
407
408
409 processAssemblyPropertyBindings(metaschemaConfig, lookupKey, assemblyBinding.getPropertyBindings());
410
411
412 processChoiceGroupBindings(config, assemblyBinding.getChoiceGroupBindings());
413 } else {
414 LOGGER.warn("Assembly binding in metaschema '{}' has neither 'name' nor 'target' attribute; skipping",
415 moduleUri);
416 }
417 }
418
419 List<MetaschemaBindings.MetaschemaBinding.DefineFieldBinding> fieldBindings
420 = metaschema.getDefineFieldBindings();
421 for (MetaschemaBindings.MetaschemaBinding.DefineFieldBinding fieldBinding : fieldBindings) {
422 String name = fieldBinding.getName();
423 String target = fieldBinding.getTarget();
424
425
426 String lookupKey = name != null ? name : target;
427 if (lookupKey != null) {
428 IDefinitionBindingConfiguration config = metaschemaConfig.getFieldDefinitionBindingConfig(lookupKey);
429 config = processDefinitionBindingConfiguration(config, fieldBinding.getJava());
430 metaschemaConfig.addFieldDefinitionBindingConfig(lookupKey, config);
431
432
433 processFieldPropertyBindings(metaschemaConfig, lookupKey, fieldBinding.getPropertyBindings());
434 } else {
435 LOGGER.warn("Field binding in metaschema '{}' has neither 'name' nor 'target' attribute; skipping",
436 moduleUri);
437 }
438 }
439 }
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466 private static <P, J> void processPropertyBindings(
467 @NonNull MetaschemaBindingConfiguration metaschemaConfig,
468 @NonNull String definitionName,
469 @Nullable List<P> propertyBindings,
470 @NonNull Function<P, String> nameAccessor,
471 @NonNull Function<P, J> javaAccessor,
472 @NonNull Function<J, String> collectionClassAccessor) throws BindingException {
473 if (propertyBindings == null) {
474 return;
475 }
476
477 for (P propertyBinding : propertyBindings) {
478 String propertyName = nameAccessor.apply(propertyBinding);
479 if (propertyName == null) {
480 continue;
481 }
482
483 J java = javaAccessor.apply(propertyBinding);
484 if (java == null) {
485 continue;
486 }
487
488 String collectionClassName = collectionClassAccessor.apply(java);
489 if (collectionClassName != null) {
490
491 validateCollectionClass(collectionClassName, definitionName, propertyName);
492
493 IMutablePropertyBindingConfiguration config = new DefaultPropertyBindingConfiguration();
494 config.setCollectionClassName(collectionClassName);
495 metaschemaConfig.addPropertyBindingConfig(definitionName, propertyName, config);
496 }
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514 private static void validateCollectionClass(
515 @NonNull String collectionClassName,
516 @NonNull String definitionName,
517 @NonNull String propertyName) throws BindingException {
518 Class<?> collectionClass;
519 try {
520 collectionClass = Class.forName(collectionClassName);
521 } catch (ClassNotFoundException ex) {
522 throw new BindingException(String.format(
523 "Collection class '%s' for property '%s' in definition '%s' could not be found",
524 collectionClassName, propertyName, definitionName), ex);
525 }
526
527
528 if (!Collection.class.isAssignableFrom(collectionClass) && !Map.class.isAssignableFrom(collectionClass)) {
529 throw new BindingException(String.format(
530 "Collection class '%s' for property '%s' in definition '%s' must implement "
531 + "java.util.Collection or java.util.Map",
532 collectionClassName, propertyName, definitionName));
533 }
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548 private static void processAssemblyPropertyBindings(
549 @NonNull MetaschemaBindingConfiguration metaschemaConfig,
550 @NonNull String definitionName,
551 @Nullable List<MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.PropertyBinding> propertyBindings)
552 throws BindingException {
553 processPropertyBindings(
554 metaschemaConfig,
555 definitionName,
556 propertyBindings,
557 MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.PropertyBinding::getName,
558 MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.PropertyBinding::getJava,
559 MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.PropertyBinding.Java::getCollectionClass);
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574 private static void processFieldPropertyBindings(
575 @NonNull MetaschemaBindingConfiguration metaschemaConfig,
576 @NonNull String definitionName,
577 @Nullable List<MetaschemaBindings.MetaschemaBinding.DefineFieldBinding.PropertyBinding> propertyBindings)
578 throws BindingException {
579 processPropertyBindings(
580 metaschemaConfig,
581 definitionName,
582 propertyBindings,
583 MetaschemaBindings.MetaschemaBinding.DefineFieldBinding.PropertyBinding::getName,
584 MetaschemaBindings.MetaschemaBinding.DefineFieldBinding.PropertyBinding::getJava,
585 MetaschemaBindings.MetaschemaBinding.DefineFieldBinding.PropertyBinding.Java::getCollectionClass);
586 }
587
588
589
590
591
592
593
594
595
596 private static void processChoiceGroupBindings(
597 @NonNull IDefinitionBindingConfiguration config,
598 @Nullable List<
599 MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.ChoiceGroupBinding> choiceGroupBindings) {
600 if (choiceGroupBindings == null || !(config instanceof DefaultDefinitionBindingConfiguration)) {
601 return;
602 }
603
604 DefaultDefinitionBindingConfiguration mutableConfig = (DefaultDefinitionBindingConfiguration) config;
605 for (MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.ChoiceGroupBinding choiceGroupBinding : choiceGroupBindings) {
606 String groupAsName = choiceGroupBinding.getName();
607 IChoiceGroupBindingConfiguration choiceGroupConfig
608 = new DefaultChoiceGroupBindingConfiguration(choiceGroupBinding);
609 mutableConfig.addChoiceGroupBinding(groupAsName, choiceGroupConfig);
610 }
611 }
612
613 @NonNull
614 private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration(
615 @Nullable IDefinitionBindingConfiguration oldConfig,
616 @Nullable MetaschemaBindings.MetaschemaBinding.DefineAssemblyBinding.Java java) {
617 IMutableDefinitionBindingConfiguration config = oldConfig == null
618 ? new DefaultDefinitionBindingConfiguration()
619 : new DefaultDefinitionBindingConfiguration(oldConfig);
620
621 if (java != null) {
622 String className = java.getUseClassName();
623 if (className != null) {
624 config.setClassName(ObjectUtils.notNull(className));
625 }
626
627 String baseClass = java.getExtendBaseClass();
628 if (baseClass != null) {
629 config.setQualifiedBaseClassName(ObjectUtils.notNull(baseClass));
630 }
631
632 List<String> interfaces = java.getImplementInterfaces();
633 for (String interfaceName : interfaces) {
634 config.addInterfaceToImplement(Objects.requireNonNull(interfaceName,
635 "interface name cannot be null in implement-interfaces configuration"));
636 }
637 }
638 return config;
639 }
640
641 @NonNull
642 private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration(
643 @Nullable IDefinitionBindingConfiguration oldConfig,
644 @Nullable MetaschemaBindings.MetaschemaBinding.DefineFieldBinding.Java java) {
645 IMutableDefinitionBindingConfiguration config = oldConfig == null
646 ? new DefaultDefinitionBindingConfiguration()
647 : new DefaultDefinitionBindingConfiguration(oldConfig);
648
649 if (java != null) {
650 String className = java.getUseClassName();
651 if (className != null) {
652 config.setClassName(ObjectUtils.notNull(className));
653 }
654
655 String baseClass = java.getExtendBaseClass();
656 if (baseClass != null) {
657 config.setQualifiedBaseClassName(ObjectUtils.notNull(baseClass));
658 }
659
660 List<String> interfaces = java.getImplementInterfaces();
661 for (String interfaceName : interfaces) {
662 config.addInterfaceToImplement(Objects.requireNonNull(interfaceName,
663 "interface name cannot be null in implement-interfaces configuration"));
664 }
665 }
666 return config;
667 }
668
669
670
671
672
673
674
675 public static final class MetaschemaBindingConfiguration {
676 private final Map<String, IDefinitionBindingConfiguration> assemblyBindingConfigs = new ConcurrentHashMap<>();
677 private final Map<String, IDefinitionBindingConfiguration> fieldBindingConfigs = new ConcurrentHashMap<>();
678
679 private final Map<String, Map<String, IPropertyBindingConfiguration>> propertyBindingConfigs
680 = new ConcurrentHashMap<>();
681
682 private MetaschemaBindingConfiguration() {
683 }
684
685
686
687
688
689
690
691
692
693
694 @Nullable
695 public IDefinitionBindingConfiguration getAssemblyDefinitionBindingConfig(@NonNull String name) {
696 return assemblyBindingConfigs.get(name);
697 }
698
699
700
701
702
703
704
705
706
707
708 @Nullable
709 public IDefinitionBindingConfiguration getFieldDefinitionBindingConfig(@NonNull String name) {
710 return fieldBindingConfigs.get(name);
711 }
712
713
714
715
716
717
718
719
720
721
722
723
724 @Nullable
725 public IDefinitionBindingConfiguration addAssemblyDefinitionBindingConfig(@NonNull String name,
726 @NonNull IDefinitionBindingConfiguration config) {
727 return assemblyBindingConfigs.put(name, config);
728 }
729
730
731
732
733
734
735
736
737
738
739
740
741 @Nullable
742 public IDefinitionBindingConfiguration addFieldDefinitionBindingConfig(@NonNull String name,
743 @NonNull IDefinitionBindingConfiguration config) {
744 return fieldBindingConfigs.put(name, config);
745 }
746
747
748
749
750
751
752
753
754
755
756
757
758 @Nullable
759 public IPropertyBindingConfiguration getPropertyBindingConfig(
760 @NonNull String definitionName,
761 @NonNull String propertyName) {
762 Map<String, IPropertyBindingConfiguration> defProps = propertyBindingConfigs.get(definitionName);
763 return defProps == null ? null : defProps.get(propertyName);
764 }
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779 @Nullable
780 public IPropertyBindingConfiguration addPropertyBindingConfig(
781 @NonNull String definitionName,
782 @NonNull String propertyName,
783 @NonNull IPropertyBindingConfiguration config) {
784 return propertyBindingConfigs
785 .computeIfAbsent(definitionName, k -> new ConcurrentHashMap<>())
786 .put(propertyName, config);
787 }
788 }
789 }