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