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.AbstractFieldInstance;
11  import gov.nist.secauto.metaschema.core.model.IBoundObject;
12  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
14  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
15  import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
16  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
17  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
18  import gov.nist.secauto.metaschema.databind.model.IBoundModule;
19  import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
20  import gov.nist.secauto.metaschema.databind.model.IGroupAs;
21  import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
22  import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs;
23  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
24  import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
25  
26  import java.lang.reflect.Field;
27  import java.util.Map;
28  import java.util.function.Predicate;
29  
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
32  import nl.talsmasoftware.lazy4j.Lazy;
33  
34  /**
35   * Implements a Metaschema module field instance bound to a Java field,
36   * supported by a bound definition class.
37   */
38  public final class InstanceModelFieldComplex
39      extends AbstractFieldInstance<
40          IBoundDefinitionModelAssembly,
41          IBoundDefinitionModelFieldComplex,
42          IBoundInstanceModelFieldComplex,
43          IBoundDefinitionModelAssembly>
44      implements IBoundInstanceModelFieldComplex, IFeatureInstanceModelGroupAs<IBoundObject> {
45    @NonNull
46    private final Field javaField;
47    @NonNull
48    private final BoundField annotation;
49    @NonNull
50    private final Lazy<IModelInstanceCollectionInfo<IBoundObject>> collectionInfo;
51    @NonNull
52    private final IGroupAs groupAs;
53    @NonNull
54    private final DefinitionField definition;
55    @NonNull
56    private final Lazy<Object> defaultValue;
57    @NonNull
58    private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
59  
60    @NonNull
61    public static InstanceModelFieldComplex newInstance(
62        @NonNull Field javaField,
63        @NonNull DefinitionField definition,
64        @NonNull IBoundDefinitionModelAssembly parent) {
65      BoundField annotation = ModelUtil.getAnnotation(javaField, BoundField.class);
66      if (!annotation.inXmlWrapped()) {
67        if (definition.hasChildren()) { // NOPMD efficiency
68          throw new IllegalStateException(
69              String.format("Field '%s' on class '%s' is requested to be unwrapped, but it has flags preventing this.",
70                  javaField.getName(),
71                  parent.getBoundClass().getName()));
72        }
73        if (!definition.getJavaTypeAdapter().isUnrappedValueAllowedInXml()) {
74          throw new IllegalStateException(
75              String.format(
76                  "Field '%s' on class '%s' is requested to be unwrapped, but its data type '%s' does not allow this.",
77                  javaField.getName(),
78                  parent.getBoundClass().getName(),
79                  definition.getJavaTypeAdapter().getPreferredName()));
80        }
81      }
82  
83      IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(
84          annotation.groupAs(),
85          parent.getContainingModule());
86      if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
87        if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
88          throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
89              javaField.getName(),
90              javaField.getDeclaringClass().getName(),
91              GroupAs.class.getName())); // NOPMD false positive
92        }
93      } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
94        // max is 1 and a groupAs is set
95        throw new IllegalStateException(
96            String.format(
97                "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
98                javaField.getName(),
99                javaField.getDeclaringClass().getName(),
100               GroupAs.class.getName())); // NOPMD false positive
101     }
102     return new InstanceModelFieldComplex(javaField, annotation, groupAs, definition, parent);
103   }
104 
105   /**
106    * Construct a new field instance bound to a Java field, supported by a bound
107    * definition class.
108    *
109    * @param javaField
110    *          the Java field bound to this instance
111    * @param definition
112    *          the assembly definition this instance is bound to
113    * @param parent
114    *          the definition containing this instance
115    */
116   @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
117   private InstanceModelFieldComplex(
118       @NonNull Field javaField,
119       @NonNull BoundField annotation,
120       @NonNull IGroupAs groupAs,
121       @NonNull DefinitionField definition,
122       @NonNull IBoundDefinitionModelAssembly parent) {
123     super(parent);
124     this.javaField = javaField;
125     this.annotation = annotation;
126     this.collectionInfo = ObjectUtils.notNull(Lazy.lazy(() -> IModelInstanceCollectionInfo.of(this)));
127     this.groupAs = groupAs;
128     this.definition = definition;
129 
130     this.defaultValue = ObjectUtils.notNull(Lazy.lazy(() -> {
131       Object retval = null;
132       if (getMaxOccurs() == 1) {
133         IBoundFieldValue fieldValue = definition.getFieldValue();
134 
135         Object fieldValueDefault = fieldValue.getDefaultValue();
136         if (fieldValueDefault != null) {
137           retval = newInstance(null);
138           fieldValue.setValue(retval, fieldValueDefault);
139 
140           for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
141             Object flagDefault = flag.getResolvedDefaultValue();
142             if (flagDefault != null) {
143               flag.setValue(retval, flagDefault);
144             }
145           }
146         }
147       }
148       return retval;
149     }));
150     this.jsonProperties = ObjectUtils.notNull(Lazy.lazy(() -> {
151       Predicate<IBoundInstanceFlag> flagFilter = null;
152       IBoundInstanceFlag jsonKey = getEffectiveJsonKey();
153       if (jsonKey != null) {
154         flagFilter = flag -> !jsonKey.equals(flag);
155       }
156       return getDefinition().getJsonProperties(flagFilter);
157     }));
158   }
159 
160   // ------------------------------------------
161   // - Start annotation driven code - CPD-OFF -
162   // ------------------------------------------
163 
164   @Override
165   public Field getField() {
166     return javaField;
167   }
168 
169   /**
170    * Get the binding Java annotation.
171    *
172    * @return the binding Java annotation
173    */
174   @NonNull
175   public BoundField getAnnotation() {
176     return annotation;
177   }
178 
179   @SuppressWarnings("null")
180   @Override
181   public IModelInstanceCollectionInfo<IBoundObject> getCollectionInfo() {
182     return collectionInfo.get();
183   }
184 
185   @Override
186   public DefinitionField getDefinition() {
187     return definition;
188   }
189 
190   @Override
191   public IBoundModule getContainingModule() {
192     return getContainingDefinition().getContainingModule();
193   }
194 
195   @Override
196   public Object getDefaultValue() {
197     return defaultValue.get();
198   }
199 
200   @Override
201   public Map<String, IBoundProperty<?>> getJsonProperties() {
202     return ObjectUtils.notNull(jsonProperties.get());
203   }
204 
205   @Override
206   public IGroupAs getGroupAs() {
207     return groupAs;
208   }
209 
210   @Override
211   public String getFormalName() {
212     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
213   }
214 
215   @Override
216   public MarkupLine getDescription() {
217     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
218   }
219 
220   @Override
221   public String getUseName() {
222     return ModelUtil.resolveNoneOrValue(getAnnotation().useName());
223   }
224 
225   @Override
226   public Integer getUseIndex() {
227     int value = getAnnotation().useIndex();
228     return value == Integer.MIN_VALUE ? null : value;
229   }
230 
231   @Override
232   public boolean isInXmlWrapped() {
233     return getAnnotation().inXmlWrapped();
234   }
235 
236   @Override
237   public int getMinOccurs() {
238     return getAnnotation().minOccurs();
239   }
240 
241   @Override
242   public int getMaxOccurs() {
243     return getAnnotation().maxOccurs();
244   }
245 
246   @Override
247   public MarkupMultiline getRemarks() {
248     return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
249   }
250 }