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.binding.metaschema.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       IBoundInstanceModelGroupedAssembly objInstance
103           = (IBoundInstanceModelGroupedAssembly) instance.getItemInstance(obj);
104 
105       if (obj instanceof AssemblyModel.ChoiceGroup.Assembly) {
106         IAssemblyInstanceGrouped assembly = newInstance(
107             (AssemblyModel.ChoiceGroup.Assembly) obj,
108             objInstance,
109             assemblyReferencePosition++,
110             parent);
111         addInstance(assembly, namedModelInstances, assemblyInstances);
112       } else if (obj instanceof AssemblyModel.ChoiceGroup.DefineAssembly) {
113         IAssemblyInstanceGrouped assembly = new InstanceModelGroupedAssemblyInline(
114             (AssemblyModel.ChoiceGroup.DefineAssembly) obj,
115             objInstance,
116             assemblyInlineDefinitionPosition++,
117             parent,
118             nodeItemFactory);
119         addInstance(assembly, namedModelInstances, assemblyInstances);
120       } else if (obj instanceof AssemblyModel.ChoiceGroup.Field) {
121         IFieldInstanceGrouped field = newInstance(
122             (AssemblyModel.ChoiceGroup.Field) obj,
123             objInstance,
124             fieldReferencePosition++,
125             parent);
126         addInstance(field, namedModelInstances, fieldInstances);
127       } else if (obj instanceof AssemblyModel.ChoiceGroup.DefineField) {
128         IFieldInstanceGrouped field = new InstanceModelGroupedFieldInline(
129             (AssemblyModel.ChoiceGroup.DefineField) obj,
130             objInstance,
131             fieldInlineDefinitionPosition++,
132             parent);
133         addInstance(field, namedModelInstances, fieldInstances);
134       } else {
135         throw new UnsupportedOperationException(
136             String.format("Unknown choice group model instance class: %s", obj.getClass()));
137       }
138     }
139 
140     this.namedModelInstances = namedModelInstances.isEmpty()
141         ? CollectionUtil.emptyMap()
142         : CollectionUtil.unmodifiableMap(namedModelInstances);
143     this.fieldInstances = fieldInstances.isEmpty()
144         ? CollectionUtil.emptyMap()
145         : CollectionUtil.unmodifiableMap(fieldInstances);
146     this.assemblyInstances = assemblyInstances.isEmpty()
147         ? CollectionUtil.emptyMap()
148         : CollectionUtil.unmodifiableMap(assemblyInstances);
149   }
150 
151   protected static void addInstance(
152       @NonNull IAssemblyInstanceGrouped assembly,
153       @NonNull Map<QName, INamedModelInstanceGrouped> namedModelInstances,
154       @NonNull Map<QName, IAssemblyInstanceGrouped> assemblyInstances) {
155     QName effectiveName = assembly.getXmlQName();
156     namedModelInstances.put(effectiveName, assembly);
157     assemblyInstances.put(effectiveName, assembly);
158   }
159 
160   protected static void addInstance(
161       @NonNull IFieldInstanceGrouped field,
162       @NonNull Map<QName, INamedModelInstanceGrouped> namedModelInstances,
163       @NonNull Map<QName, IFieldInstanceGrouped> fieldInstances) {
164     QName effectiveName = field.getXmlQName();
165     namedModelInstances.put(effectiveName, field);
166     fieldInstances.put(effectiveName, field);
167   }
168 
169   @NonNull
170   protected static IAssemblyInstanceGrouped newInstance(
171       @NonNull AssemblyModel.ChoiceGroup.Assembly obj,
172       @NonNull IBoundInstanceModelGroupedAssembly objInstance,
173       int position,
174       @NonNull IChoiceGroupInstance parent) {
175     IAssemblyDefinition owningDefinition = parent.getOwningDefinition();
176     IModule module = owningDefinition.getContainingModule();
177 
178     QName name = parent.getContainingModule().toModelQName(ObjectUtils.requireNonNull(obj.getRef()));
179     IAssemblyDefinition definition = module.getScopedAssemblyDefinitionByName(name);
180 
181     if (definition == null) {
182       throw new IllegalStateException(
183           String.format("Unable to resolve assembly reference '%s' in definition '%s' in module '%s'",
184               name,
185               owningDefinition.getName(),
186               module.getShortName()));
187     }
188     return new InstanceModelGroupedAssemblyReference(obj, objInstance, position, definition, parent);
189   }
190 
191   @NonNull
192   protected static IFieldInstanceGrouped newInstance(
193       @NonNull AssemblyModel.ChoiceGroup.Field obj,
194       @NonNull IBoundInstanceModelGroupedAssembly objInstance,
195       int position,
196       @NonNull IChoiceGroupInstance parent) {
197     IAssemblyDefinition owningDefinition = parent.getOwningDefinition();
198     IModule module = owningDefinition.getContainingModule();
199 
200     QName name = parent.getContainingModule().toModelQName(ObjectUtils.requireNonNull(obj.getRef()));
201     IFieldDefinition definition = module.getScopedFieldDefinitionByName(name);
202     if (definition == null) {
203       throw new IllegalStateException(
204           String.format("Unable to resolve field reference '%s' in definition '%s' in module '%s'",
205               name,
206               owningDefinition.getName(),
207               module.getShortName()));
208     }
209     return new InstanceModelGroupedFieldReference(obj, objInstance, position, definition, parent);
210   }
211 
212   @SuppressWarnings("null")
213   @Override
214   public Collection<INamedModelInstanceGrouped> getModelInstances() {
215     return namedModelInstances.values();
216   }
217 
218   @Override
219   public Map<QName, INamedModelInstanceGrouped> getNamedModelInstanceMap() {
220     return namedModelInstances;
221   }
222 
223   @Override
224   public Map<QName, IFieldInstanceGrouped> getFieldInstanceMap() {
225     return fieldInstances;
226   }
227 
228   @Override
229   public Map<QName, IAssemblyInstanceGrouped> getAssemblyInstanceMap() {
230     return assemblyInstances;
231   }
232 }