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