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     this.javaField = javaField;
122     this.annotation = annotation;
123     this.collectionInfo = ObjectUtils.notNull(Lazy.lazy(() -> IModelInstanceCollectionInfo.of(this)));
124     this.groupAs = groupAs;
125     this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
126         annotation.typeAdapter(),
127         parent.getBindingContext());
128     this.defaultValue = ModelUtil.resolveDefaultValue(annotation.defaultValue(), this.javaTypeAdapter);
129 
130     IModule module = getContainingModule();
131     ISource source = module.getSource();
132 
133     this.constraints = ObjectUtils.notNull(Lazy.lazy(() -> {
134       IValueConstrained retval = new ValueConstraintSet(source);
135       ValueConstraints valueAnnotation = annotation.valueConstraints();
136       ConstraintSupport.parse(valueAnnotation, module.getSource(), retval);
137       return retval;
138     }));
139     this.properties = ObjectUtils.notNull(
140         Lazy.lazy(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
141             Arrays.stream(annotation.properties())
142                 .map(ModelUtil::toPropertyEntry)
143                 .collect(
144                     Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
145   }
146 
147   // ------------------------------------------
148   // - Start annotation driven code - CPD-OFF -
149   // ------------------------------------------
150 
151   @Override
152   public IBindingContext getBindingContext() {
153     return getContainingDefinition().getBindingContext();
154   }
155 
156   @Override
157   public IBoundModule getContainingModule() {
158     return getContainingDefinition().getContainingModule();
159   }
160 
161   @Override
162   public Field getField() {
163     return javaField;
164   }
165 
166   /**
167    * Get the binding Java annotation.
168    *
169    * @return the binding Java annotation
170    */
171   @NonNull
172   public BoundField getAnnotation() {
173     return annotation;
174   }
175 
176   @SuppressWarnings("null")
177   @Override
178   public IModelInstanceCollectionInfo<Object> getCollectionInfo() {
179     return collectionInfo.get();
180   }
181 
182   @SuppressWarnings("null")
183   @Override
184   @NonNull
185   public IValueConstrained getConstraintSupport() {
186     return constraints.get();
187   }
188 
189   @Override
190   public IDataTypeAdapter<?> getJavaTypeAdapter() {
191     return javaTypeAdapter;
192   }
193 
194   @Override
195   public Object getDefaultValue() {
196     return defaultValue;
197   }
198 
199   @Override
200   public IGroupAs getGroupAs() {
201     return groupAs;
202   }
203 
204   @Override
205   public String getFormalName() {
206     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
207   }
208 
209   @Override
210   public MarkupLine getDescription() {
211     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
212   }
213 
214   @Override
215   public Integer getUseIndex() {
216     int value = getAnnotation().useIndex();
217     return value == Integer.MIN_VALUE ? null : value;
218   }
219 
220   @Override
221   public boolean isInXmlWrapped() {
222     return getAnnotation().inXmlWrapped();
223   }
224 
225   @Override
226   public int getMinOccurs() {
227     return getAnnotation().minOccurs();
228   }
229 
230   @Override
231   public int getMaxOccurs() {
232     return getAnnotation().maxOccurs();
233   }
234 
235   @Override
236   public Map<Key, Set<String>> getProperties() {
237     return ObjectUtils.notNull(properties.get());
238   }
239 
240   @Override
241   public MarkupMultiline getRemarks() {
242     return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
243   }
244 
245   @Override
246   public String getName() {
247     // the name is stored as a usename to remain consistent with non-scalar valued
248     // fields
249     return ObjectUtils.notNull(
250         Optional.ofNullable(ModelUtil.resolveNoneOrValue(getAnnotation().useName())).orElse(getField().getName()));
251   }
252 
253   // ----------------------------------------
254   // - End annotation driven code - CPD-OFF -
255   // ----------------------------------------
256 }