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.IDataTypeAdapter;
9   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
10  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
11  import gov.nist.secauto.metaschema.core.model.AbstractInlineFieldDefinition;
12  import gov.nist.secauto.metaschema.core.model.IModule;
13  import gov.nist.secauto.metaschema.core.model.constraint.ISource;
14  import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
15  import gov.nist.secauto.metaschema.core.model.constraint.ValueConstraintSet;
16  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
17  import gov.nist.secauto.metaschema.databind.IBindingContext;
18  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
19  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelField;
20  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
21  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
22  import gov.nist.secauto.metaschema.databind.model.IBoundModule;
23  import gov.nist.secauto.metaschema.databind.model.IGroupAs;
24  import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
25  import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs;
26  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
27  import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
28  import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
29  
30  import java.lang.reflect.Field;
31  import java.util.Optional;
32  
33  import edu.umd.cs.findbugs.annotations.NonNull;
34  import edu.umd.cs.findbugs.annotations.Nullable;
35  import nl.talsmasoftware.lazy4j.Lazy;
36  
37  public final class InstanceModelFieldScalar
38      extends AbstractInlineFieldDefinition<
39          IBoundDefinitionModelAssembly,
40          IBoundDefinitionModelField<Object>,
41          IBoundInstanceModelFieldScalar,
42          IBoundDefinitionModelAssembly,
43          IBoundInstanceFlag>
44      implements IBoundInstanceModelFieldScalar, IFeatureInstanceModelGroupAs<Object> {
45    @NonNull
46    private final Field javaField;
47    @NonNull
48    private final BoundField annotation;
49    @NonNull
50    private final Lazy<IModelInstanceCollectionInfo<Object>> collectionInfo;
51    @NonNull
52    private final IGroupAs groupAs;
53    @NonNull
54    private final IDataTypeAdapter<?> javaTypeAdapter;
55    @Nullable
56    private final Object defaultValue;
57    @NonNull
58    private final Lazy<IValueConstrained> constraints;
59  
60    /**
61     * Construct a new field instance bound to a Java field.
62     *
63     * @param javaField
64     *          the Java field bound to this instance
65     * @param annotation
66     *          the field binding annotation
67     * @param groupAs
68     *          the grouping info for the model instance
69     * @param containingDefinition
70     *          the definition containing this instance
71     * @return the instance
72     */
73    @NonNull
74    public static InstanceModelFieldScalar newInstance(
75        @NonNull Field javaField,
76        @NonNull IBoundDefinitionModelAssembly containingDefinition) {
77      BoundField annotation = ModelUtil.getAnnotation(javaField, BoundField.class);
78      IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(
79          annotation.groupAs(),
80          containingDefinition.getContainingModule());
81  
82      if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
83        if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
84          throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
85              javaField.getName(),
86              javaField.getDeclaringClass().getName(),
87              GroupAs.class.getName())); // NOPMD false positive
88        }
89      } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
90        // max is 1 and a groupAs is set
91        throw new IllegalStateException(
92            String.format(
93                "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
94                javaField.getName(),
95                javaField.getDeclaringClass().getName(),
96                GroupAs.class.getName())); // NOPMD false positive
97      }
98  
99      return new InstanceModelFieldScalar(
100         javaField,
101         annotation,
102         groupAs,
103         containingDefinition);
104   }
105 
106   private InstanceModelFieldScalar(
107       @NonNull Field javaField,
108       @NonNull BoundField annotation,
109       @NonNull IGroupAs groupAs,
110       @NonNull IBoundDefinitionModelAssembly containingDefinition) {
111     super(containingDefinition);
112     this.javaField = javaField;
113     this.annotation = annotation;
114     this.collectionInfo = ObjectUtils.notNull(Lazy.lazy(() -> IModelInstanceCollectionInfo.of(this)));
115     this.groupAs = groupAs;
116     this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
117         annotation.typeAdapter(),
118         containingDefinition.getBindingContext());
119     this.defaultValue = ModelUtil.resolveDefaultValue(annotation.defaultValue(), this.javaTypeAdapter);
120 
121     IModule module = getContainingModule();
122 
123     this.constraints = ObjectUtils.notNull(Lazy.lazy(() -> {
124       IValueConstrained retval = new ValueConstraintSet();
125       ValueConstraints valueAnnotation = annotation.valueConstraints();
126       ConstraintSupport.parse(valueAnnotation, ISource.modelSource(module), retval);
127       return retval;
128     }));
129   }
130 
131   // ------------------------------------------
132   // - Start annotation driven code - CPD-OFF -
133   // ------------------------------------------
134 
135   @Override
136   public IBindingContext getBindingContext() {
137     return getContainingDefinition().getBindingContext();
138   }
139 
140   @Override
141   public IBoundModule getContainingModule() {
142     return getContainingDefinition().getContainingModule();
143   }
144 
145   @Override
146   public Field getField() {
147     return javaField;
148   }
149 
150   /**
151    * Get the binding Java annotation.
152    *
153    * @return the binding Java annotation
154    */
155   @NonNull
156   public BoundField getAnnotation() {
157     return annotation;
158   }
159 
160   @SuppressWarnings("null")
161   @Override
162   public IModelInstanceCollectionInfo<Object> getCollectionInfo() {
163     return collectionInfo.get();
164   }
165 
166   @SuppressWarnings("null")
167   @Override
168   @NonNull
169   public IValueConstrained getConstraintSupport() {
170     return constraints.get();
171   }
172 
173   @Override
174   public IDataTypeAdapter<?> getJavaTypeAdapter() {
175     return javaTypeAdapter;
176   }
177 
178   @Override
179   public Object getDefaultValue() {
180     return defaultValue;
181   }
182 
183   @Override
184   public IGroupAs getGroupAs() {
185     return groupAs;
186   }
187 
188   @Override
189   public String getFormalName() {
190     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
191   }
192 
193   @Override
194   public MarkupLine getDescription() {
195     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
196   }
197 
198   @Override
199   public Integer getUseIndex() {
200     int value = getAnnotation().useIndex();
201     return value == Integer.MIN_VALUE ? null : value;
202   }
203 
204   @Override
205   public boolean isInXmlWrapped() {
206     return getAnnotation().inXmlWrapped();
207   }
208 
209   @Override
210   public int getMinOccurs() {
211     return getAnnotation().minOccurs();
212   }
213 
214   @Override
215   public int getMaxOccurs() {
216     return getAnnotation().maxOccurs();
217   }
218 
219   @Override
220   public MarkupMultiline getRemarks() {
221     return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
222   }
223 
224   @Override
225   public String getName() {
226     // the name is stored as a usename to remain consistent with non-scalar valued
227     // fields
228     return ObjectUtils.notNull(
229         Optional.ofNullable(ModelUtil.resolveNoneOrValue(getAnnotation().useName())).orElse(getField().getName()));
230   }
231 
232   // ----------------------------------------
233   // - End annotation driven code - CPD-OFF -
234   // ----------------------------------------
235 }