1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.metaschema.impl;
7   
8   import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
9   import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
10  import gov.nist.secauto.metaschema.core.model.IAssemblyInstanceGrouped;
11  import gov.nist.secauto.metaschema.core.model.IChoiceGroupInstance;
12  import gov.nist.secauto.metaschema.core.model.IContainerModelSupport;
13  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
14  import gov.nist.secauto.metaschema.core.model.IFieldInstanceGrouped;
15  import gov.nist.secauto.metaschema.core.model.IModule;
16  import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped;
17  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
18  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
20  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
21  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.AssemblyModel;
22  
23  import java.util.Collection;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.xml.namespace.QName;
29  
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  import edu.umd.cs.findbugs.annotations.Nullable;
32  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
33  
34  class ChoiceGroupModelContainerSupport
35      implements IContainerModelSupport<
36          INamedModelInstanceGrouped,
37          INamedModelInstanceGrouped,
38          IFieldInstanceGrouped,
39          IAssemblyInstanceGrouped> {
40    @NonNull
41    private final Map<QName, INamedModelInstanceGrouped> namedModelInstances;
42    @NonNull
43    private final Map<QName, IFieldInstanceGrouped> fieldInstances;
44    @NonNull
45    private final Map<QName, IAssemblyInstanceGrouped> assemblyInstances;
46  
47    @SuppressWarnings("PMD.ShortMethodName")
48    public static IContainerModelSupport<
49        INamedModelInstanceGrouped,
50        INamedModelInstanceGrouped,
51        IFieldInstanceGrouped,
52        IAssemblyInstanceGrouped> of(
53            @Nullable AssemblyModel.ChoiceGroup binding,
54            @NonNull IBoundInstanceModelGroupedAssembly bindingInstance,
55            @NonNull IChoiceGroupInstance parent,
56            @NonNull INodeItemFactory nodeItemFactory) {
57      List<Object> instances;
58      return binding == null || (instances = binding.getChoices()) == null || instances.isEmpty()
59          ? IContainerModelSupport.empty()
60          : new ChoiceGroupModelContainerSupport(
61              binding,
62              bindingInstance,
63              parent,
64              nodeItemFactory);
65    }
66  
67    /**
68     * Construct a new assembly model container.
69     *
70     * @param binding
71     *          the choice model object bound to a Java class
72     * @param bindingInstance
73     *          the Metaschema module instance for the bound model object
74     * @param parent
75     *          the assembly definition containing this container
76     * @param nodeItemFactory
77     *          the node item factory used to generate child nodes
78     */
79    @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseConcurrentHashMap", "PMD.PrematureDeclaration" })
80    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
81    public ChoiceGroupModelContainerSupport(
82        @NonNull AssemblyModel.ChoiceGroup binding,
83        @NonNull IBoundInstanceModelGroupedAssembly bindingInstance,
84        @NonNull IChoiceGroupInstance parent,
85        @NonNull INodeItemFactory nodeItemFactory) {
86  
87      // create temporary collections to store the child binding objects
88      final Map<QName, INamedModelInstanceGrouped> namedModelInstances = new LinkedHashMap<>();
89      final Map<QName, IFieldInstanceGrouped> fieldInstances = new LinkedHashMap<>();
90      final Map<QName, IAssemblyInstanceGrouped> assemblyInstances = new LinkedHashMap<>();
91  
92      // create counters to track child positions
93      int assemblyReferencePosition = 0;
94      int assemblyInlineDefinitionPosition = 0;
95      int fieldReferencePosition = 0;
96      int fieldInlineDefinitionPosition = 0;
97  
98      // TODO: make "instances" a constant
99      IBoundInstanceModelChoiceGroup instance = ObjectUtils.requireNonNull(
100         bindingInstance.getDefinition().getChoiceGroupInstanceByName("choices"));
101     for (Object obj : ObjectUtils.notNull(binding.getChoices())) {
102       assert obj != null;
103 
104       IBoundInstanceModelGroupedAssembly objInstance
105           = (IBoundInstanceModelGroupedAssembly) instance.getItemInstance(obj);
106 
107       if (obj instanceof AssemblyModel.ChoiceGroup.Assembly) {
108         IAssemblyInstanceGrouped assembly = newInstance(
109             (AssemblyModel.ChoiceGroup.Assembly) obj,
110             objInstance,
111             assemblyReferencePosition++,
112             parent);
113         addInstance(assembly, namedModelInstances, assemblyInstances);
114       } else if (obj instanceof AssemblyModel.ChoiceGroup.DefineAssembly) {
115         IAssemblyInstanceGrouped assembly = new InstanceModelGroupedAssemblyInline(
116             (AssemblyModel.ChoiceGroup.DefineAssembly) obj,
117             objInstance,
118             assemblyInlineDefinitionPosition++,
119             parent,
120             nodeItemFactory);
121         addInstance(assembly, namedModelInstances, assemblyInstances);
122       } else if (obj instanceof AssemblyModel.ChoiceGroup.Field) {
123         IFieldInstanceGrouped field = newInstance(
124             (AssemblyModel.ChoiceGroup.Field) obj,
125             objInstance,
126             fieldReferencePosition++,
127             parent);
128         addInstance(field, namedModelInstances, fieldInstances);
129       } else if (obj instanceof AssemblyModel.ChoiceGroup.DefineField) {
130         IFieldInstanceGrouped field = new InstanceModelGroupedFieldInline(
131             (AssemblyModel.ChoiceGroup.DefineField) obj,
132             objInstance,
133             fieldInlineDefinitionPosition++,
134             parent);
135         addInstance(field, namedModelInstances, fieldInstances);
136       } else {
137         throw new UnsupportedOperationException(
138             String.format("Unknown choice group model instance class: %s", obj.getClass()));
139       }
140     }
141 
142     this.namedModelInstances = namedModelInstances.isEmpty()
143         ? CollectionUtil.emptyMap()
144         : CollectionUtil.unmodifiableMap(namedModelInstances);
145     this.fieldInstances = fieldInstances.isEmpty()
146         ? CollectionUtil.emptyMap()
147         : CollectionUtil.unmodifiableMap(fieldInstances);
148     this.assemblyInstances = assemblyInstances.isEmpty()
149         ? CollectionUtil.emptyMap()
150         : CollectionUtil.unmodifiableMap(assemblyInstances);
151   }
152 
153   protected static void addInstance(
154       @NonNull IAssemblyInstanceGrouped assembly,
155       @NonNull Map<QName, INamedModelInstanceGrouped> namedModelInstances,
156       @NonNull Map<QName, IAssemblyInstanceGrouped> assemblyInstances) {
157     QName effectiveName = assembly.getXmlQName();
158     namedModelInstances.put(effectiveName, assembly);
159     assemblyInstances.put(effectiveName, assembly);
160   }
161 
162   protected static void addInstance(
163       @NonNull IFieldInstanceGrouped field,
164       @NonNull Map<QName, INamedModelInstanceGrouped> namedModelInstances,
165       @NonNull Map<QName, IFieldInstanceGrouped> fieldInstances) {
166     QName effectiveName = field.getXmlQName();
167     namedModelInstances.put(effectiveName, field);
168     fieldInstances.put(effectiveName, field);
169   }
170 
171   @NonNull
172   protected static IAssemblyInstanceGrouped newInstance(
173       @NonNull AssemblyModel.ChoiceGroup.Assembly obj,
174       @NonNull IBoundInstanceModelGroupedAssembly objInstance,
175       int position,
176       @NonNull IChoiceGroupInstance parent) {
177     IAssemblyDefinition owningDefinition = parent.getOwningDefinition();
178     IModule module = owningDefinition.getContainingModule();
179 
180     QName name = parent.getContainingModule().toModelQName(ObjectUtils.requireNonNull(obj.getRef()));
181     IAssemblyDefinition definition = module.getScopedAssemblyDefinitionByName(name);
182 
183     if (definition == null) {
184       throw new IllegalStateException(
185           String.format("Unable to resolve assembly reference '%s' in definition '%s' in module '%s'",
186               name,
187               owningDefinition.getName(),
188               module.getShortName()));
189     }
190     return new InstanceModelGroupedAssemblyReference(obj, objInstance, position, definition, parent);
191   }
192 
193   @NonNull
194   protected static IFieldInstanceGrouped newInstance(
195       @NonNull AssemblyModel.ChoiceGroup.Field obj,
196       @NonNull IBoundInstanceModelGroupedAssembly objInstance,
197       int position,
198       @NonNull IChoiceGroupInstance parent) {
199     IAssemblyDefinition owningDefinition = parent.getOwningDefinition();
200     IModule module = owningDefinition.getContainingModule();
201 
202     QName name = parent.getContainingModule().toModelQName(ObjectUtils.requireNonNull(obj.getRef()));
203     IFieldDefinition definition = module.getScopedFieldDefinitionByName(name);
204     if (definition == null) {
205       throw new IllegalStateException(
206           String.format("Unable to resolve field reference '%s' in definition '%s' in module '%s'",
207               name,
208               owningDefinition.getName(),
209               module.getShortName()));
210     }
211     return new InstanceModelGroupedFieldReference(obj, objInstance, position, definition, parent);
212   }
213 
214   @SuppressWarnings("null")
215   @Override
216   public Collection<INamedModelInstanceGrouped> getModelInstances() {
217     return namedModelInstances.values();
218   }
219 
220   @Override
221   public Map<QName, INamedModelInstanceGrouped> getNamedModelInstanceMap() {
222     return namedModelInstances;
223   }
224 
225   @Override
226   public Map<QName, IFieldInstanceGrouped> getFieldInstanceMap() {
227     return fieldInstances;
228   }
229 
230   @Override
231   public Map<QName, IAssemblyInstanceGrouped> getAssemblyInstanceMap() {
232     return assemblyInstances;
233   }
234 }