1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.impl;
7   
8   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
9   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
10  import gov.nist.secauto.metaschema.core.model.IAttributable;
11  import gov.nist.secauto.metaschema.core.model.IBoundObject;
12  import gov.nist.secauto.metaschema.core.model.IChoiceInstance;
13  import gov.nist.secauto.metaschema.core.model.IContainerModelAssemblySupport;
14  import gov.nist.secauto.metaschema.core.model.ISource;
15  import gov.nist.secauto.metaschema.core.model.constraint.AssemblyConstraintSet;
16  import gov.nist.secauto.metaschema.core.model.constraint.IModelConstrained;
17  import gov.nist.secauto.metaschema.core.model.util.ModuleUtils;
18  import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
19  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
20  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
21  import gov.nist.secauto.metaschema.databind.IBindingContext;
22  import gov.nist.secauto.metaschema.databind.io.BindingException;
23  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
24  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
25  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
26  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
27  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelField;
28  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelNamed;
29  import gov.nist.secauto.metaschema.databind.model.IBoundModule;
30  import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
31  import gov.nist.secauto.metaschema.databind.model.annotations.AssemblyConstraints;
32  import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly;
33  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
34  import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
35  
36  import java.util.Arrays;
37  import java.util.LinkedHashMap;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.stream.Collectors;
41  
42  import edu.umd.cs.findbugs.annotations.NonNull;
43  import edu.umd.cs.findbugs.annotations.Nullable;
44  import nl.talsmasoftware.lazy4j.Lazy;
45  
46  /**
47   * Implements a Metaschema module global assembly definition bound to a Java
48   * class.
49   */
50  @SuppressWarnings("PMD.CouplingBetweenObjects")
51  public final class DefinitionAssembly
52      extends AbstractBoundDefinitionModelComplex<MetaschemaAssembly>
53      implements IBoundDefinitionModelAssembly {
54  
55    @NonNull
56    private final Lazy<FlagContainerSupport> flagContainer;
57    @NonNull
58    private final Lazy<IContainerModelAssemblySupport<
59        IBoundInstanceModel<?>,
60        IBoundInstanceModelNamed<?>,
61        IBoundInstanceModelField<?>,
62        IBoundInstanceModelAssembly,
63        IChoiceInstance,
64        IBoundInstanceModelChoiceGroup>> modelContainer;
65    @NonNull
66    private final Lazy<IModelConstrained> constraints;
67    @NonNull
68    private final Lazy<IEnhancedQName> xmlRootQName;
69    @NonNull
70    private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
71    @NonNull
72    private final Lazy<Map<IAttributable.Key, Set<String>>> properties;
73  
74    /**
75     * Construct a new global assembly instance.
76     *
77     * @param clazz
78     *          the class the assembly is bound to
79     * @param annotation
80     *          the binding annotation associated with this class
81     * @param module
82     *          the module containing this class
83     * @param bindingContext
84     *          the Metaschema binding context managing this class used to lookup
85     *          binding information
86     * @return the definition
87     */
88    @NonNull
89    public static DefinitionAssembly newInstance(
90        @NonNull Class<? extends IBoundObject> clazz,
91        @NonNull MetaschemaAssembly annotation,
92        @NonNull IBoundModule module,
93        @NonNull IBindingContext bindingContext) {
94      return new DefinitionAssembly(clazz, annotation, module, bindingContext);
95    }
96  
97    private DefinitionAssembly(
98        @NonNull Class<? extends IBoundObject> clazz,
99        @NonNull MetaschemaAssembly annotation,
100       @NonNull IBoundModule module,
101       @NonNull IBindingContext bindingContext) {
102     super(clazz, annotation, module, bindingContext);
103 
104     String rootLocalName = ModelUtil.resolveNoneOrDefault(getAnnotation().rootName(), null);
105     this.xmlRootQName = ObjectUtils.notNull(Lazy.lazy(() -> rootLocalName == null
106         ? null
107         : ModuleUtils.parseModelName(getContainingModule(), rootLocalName)));
108     this.flagContainer = ObjectUtils.notNull(Lazy.lazy(() -> new FlagContainerSupport(this, null)));
109     this.modelContainer = ObjectUtils.notNull(Lazy.lazy(() -> AssemblyModelGenerator.of(this)));
110 
111     ISource source = module.getSource();
112     this.constraints = ObjectUtils.notNull(Lazy.lazy(() -> {
113       IModelConstrained retval = new AssemblyConstraintSet(source);
114       ValueConstraints valueAnnotation = getAnnotation().valueConstraints();
115       ConstraintSupport.parse(valueAnnotation, source, retval);
116 
117       AssemblyConstraints assemblyAnnotation = getAnnotation().modelConstraints();
118       ConstraintSupport.parse(assemblyAnnotation, source, retval);
119       return retval;
120     }));
121     this.jsonProperties = ObjectUtils.notNull(Lazy.lazy(() -> getJsonProperties(null)));
122     this.properties = ObjectUtils.notNull(
123         Lazy.lazy(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
124             Arrays.stream(annotation.properties())
125                 .map(ModelUtil::toPropertyEntry)
126                 .collect(
127                     Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
128   }
129 
130   @Override
131   protected void deepCopyItemInternal(IBoundObject fromObject, IBoundObject toObject) throws BindingException {
132     // copy the flags
133     super.deepCopyItemInternal(fromObject, toObject);
134 
135     for (IBoundInstanceModel<?> instance : getModelInstances()) {
136       assert instance != null;
137 
138       instance.deepCopy(fromObject, toObject);
139     }
140   }
141 
142   @Override
143   public Map<String, IBoundProperty<?>> getJsonProperties() {
144     return ObjectUtils.notNull(jsonProperties.get());
145   }
146 
147   // ------------------------------------------
148   // - Start annotation driven code - CPD-OFF -
149   // ------------------------------------------
150 
151   @Override
152   @SuppressWarnings("null")
153   @NonNull
154   public FlagContainerSupport getFlagContainer() {
155     return flagContainer.get();
156   }
157 
158   @Override
159   @SuppressWarnings("null")
160   @NonNull
161   public IContainerModelAssemblySupport<
162       IBoundInstanceModel<?>,
163       IBoundInstanceModelNamed<?>,
164       IBoundInstanceModelField<?>,
165       IBoundInstanceModelAssembly,
166       IChoiceInstance,
167       IBoundInstanceModelChoiceGroup> getModelContainer() {
168     return modelContainer.get();
169   }
170 
171   @Override
172   @NonNull
173   public IModelConstrained getConstraintSupport() {
174     return ObjectUtils.notNull(constraints.get());
175   }
176 
177   @Override
178   @Nullable
179   public String getFormalName() {
180     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
181   }
182 
183   @Override
184   @Nullable
185   public MarkupLine getDescription() {
186     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
187   }
188 
189   @Override
190   @NonNull
191   public String getName() {
192     return getAnnotation().name();
193   }
194 
195   @Override
196   @Nullable
197   public Integer getIndex() {
198     return ModelUtil.resolveDefaultInteger(getAnnotation().index());
199   }
200 
201   @Override
202   public Map<Key, Set<String>> getProperties() {
203     return ObjectUtils.notNull(properties.get());
204   }
205 
206   @Override
207   @Nullable
208   public MarkupMultiline getRemarks() {
209     return ModelUtil.resolveToMarkupMultiline(getAnnotation().description());
210   }
211 
212   @Override
213   @Nullable
214   public IEnhancedQName getRootQName() {
215     // Overriding this is more efficient, since it is already built
216     return xmlRootQName.get();
217   }
218 
219   @Override
220   public boolean isRoot() {
221     // Overriding this is more efficient, since the root name is derived from the
222     // XML QName
223     return getRootQName() != null;
224   }
225 
226   @Override
227   @Nullable
228   public String getRootName() {
229     // Overriding this is more efficient, since it is already built
230     IEnhancedQName qname = getRootQName();
231     return qname == null ? null : qname.getLocalName();
232   }
233 
234   @Override
235   @Nullable
236   public Integer getRootIndex() {
237     return ModelUtil.resolveDefaultInteger(getAnnotation().rootIndex());
238   }
239 
240   // ----------------------------------------
241   // - End annotation driven code - CPD-OFF -
242   // ----------------------------------------
243 }