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