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.IBoundObject;
12  import gov.nist.secauto.metaschema.core.model.IModule;
13  import gov.nist.secauto.metaschema.core.model.constraint.AssemblyConstraintSet;
14  import gov.nist.secauto.metaschema.core.model.constraint.IModelConstrained;
15  import gov.nist.secauto.metaschema.core.model.constraint.ISource;
16  import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
17  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
18  import gov.nist.secauto.metaschema.databind.IBindingContext;
19  import gov.nist.secauto.metaschema.databind.io.BindingException;
20  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
21  import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
22  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
23  import gov.nist.secauto.metaschema.databind.model.IBoundModule;
24  import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
25  import gov.nist.secauto.metaschema.databind.model.annotations.BoundFieldValue;
26  import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
27  import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
28  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
29  import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
30  
31  import java.lang.reflect.Field;
32  import java.util.Map;
33  import java.util.function.Predicate;
34  
35  import edu.umd.cs.findbugs.annotations.NonNull;
36  import edu.umd.cs.findbugs.annotations.Nullable;
37  import nl.talsmasoftware.lazy4j.Lazy;
38  
39  //TODO: implement getProperties()
40  public final class DefinitionField
41      extends AbstractBoundDefinitionModelComplex<MetaschemaField>
42      implements IBoundDefinitionModelFieldComplex {
43    @NonNull
44    private final FieldValue fieldValue;
45    @Nullable
46    private IBoundInstanceFlag jsonValueKeyFlagInstance;
47    @NonNull
48    private final Lazy<FlagContainerSupport> flagContainer;
49    @NonNull
50    private final Lazy<IValueConstrained> constraints;
51    @NonNull
52    private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
53  
54    /**
55     * Collect all fields that are part of the model for this class.
56     *
57     * @param clazz
58     *          the class
59     * @return the field value instances if found or {@code null} otherwise
60     */
61    @Nullable
62    private static Field getFieldValueField(Class<?> clazz) {
63      Field[] fields = clazz.getDeclaredFields();
64  
65      Field retval = null;
66      for (Field field : fields) {
67        if (!field.isAnnotationPresent(BoundFieldValue.class) || field.isAnnotationPresent(Ignore.class)) {
68          // skip this field, since it is ignored
69          continue;
70        }
71        retval = field;
72      }
73  
74      if (retval == null) {
75        Class<?> superClass = clazz.getSuperclass();
76        if (superClass != null) {
77          // get instances from superclass
78          retval = getFieldValueField(superClass);
79        }
80      }
81      return retval;
82    }
83  
84    /**
85     * Construct a new Metaschema module field definition.
86     *
87     * @param clazz
88     *          the Java class the definition is bound to
89     * @param bindingContext
90     *          the Metaschema binding context managing this class
91     * @return the instance
92     */
93    @NonNull
94    public static DefinitionField newInstance(
95        @NonNull Class<? extends IBoundObject> clazz,
96        @NonNull IBindingContext bindingContext) {
97      MetaschemaField annotation = ModelUtil.getAnnotation(clazz, MetaschemaField.class);
98      Class<? extends IBoundModule> moduleClass = annotation.moduleClass();
99      return new DefinitionField(clazz, annotation, moduleClass, bindingContext);
100   }
101 
102   private DefinitionField(
103       @NonNull Class<? extends IBoundObject> clazz,
104       @NonNull MetaschemaField annotation,
105       @NonNull Class<? extends IBoundModule> moduleClass,
106       @NonNull IBindingContext bindingContext) {
107     super(clazz, annotation, moduleClass, bindingContext);
108     Field field = getFieldValueField(getBoundClass());
109     if (field == null) {
110       throw new IllegalArgumentException(
111           String.format("Class '%s' is missing the '%s' annotation on one of its fields.",
112               clazz.getName(),
113               BoundFieldValue.class.getName())); // NOPMD false positive
114     }
115     this.fieldValue = new FieldValue(field, BoundFieldValue.class, bindingContext);
116     this.flagContainer = ObjectUtils.notNull(Lazy.lazy(() -> new FlagContainerSupport(this, this::handleFlagInstance)));
117 
118     IModule module = getContainingModule();
119 
120     this.constraints = ObjectUtils.notNull(Lazy.lazy(() -> {
121       IModelConstrained retval = new AssemblyConstraintSet();
122       ValueConstraints valueAnnotation = getAnnotation().valueConstraints();
123       ConstraintSupport.parse(valueAnnotation, ISource.modelSource(module), retval);
124       return retval;
125     }));
126     this.jsonProperties = ObjectUtils.notNull(Lazy.lazy(() -> {
127       IBoundInstanceFlag jsonValueKey = getJsonValueKeyFlagInstance();
128       Predicate<IBoundInstanceFlag> flagFilter = jsonValueKey == null ? null : flag -> !flag.equals(jsonValueKey);
129       return getJsonProperties(flagFilter);
130     }));
131   }
132 
133   /**
134    * A callback used to identify the JSON value key flag.
135    *
136    * @param instance
137    *          a flag instance
138    */
139   protected void handleFlagInstance(@NonNull IBoundInstanceFlag instance) {
140     if (instance.isJsonValueKey()) {
141       this.jsonValueKeyFlagInstance = instance;
142     }
143   }
144 
145   @Override
146   @NonNull
147   public FieldValue getFieldValue() {
148     return fieldValue;
149   }
150 
151   @Override
152   public IBoundInstanceFlag getJsonValueKeyFlagInstance() {
153     // lazy load flags
154     getFlagContainer();
155     return jsonValueKeyFlagInstance;
156   }
157 
158   @Override
159   protected void deepCopyItemInternal(IBoundObject fromObject, IBoundObject toObject) throws BindingException {
160     // copy the flags
161     super.deepCopyItemInternal(fromObject, toObject);
162 
163     getFieldValue().deepCopy(fromObject, toObject);
164   }
165 
166   // ------------------------------------------
167   // - Start annotation driven code - CPD-OFF -
168   // ------------------------------------------
169 
170   @Override
171   @SuppressWarnings("null")
172   @NonNull
173   public FlagContainerSupport getFlagContainer() {
174     return flagContainer.get();
175   }
176 
177   @Override
178   @NonNull
179   public IValueConstrained getConstraintSupport() {
180     return ObjectUtils.notNull(constraints.get());
181   }
182 
183   @Override
184   public Map<String, IBoundProperty<?>> getJsonProperties() {
185     return ObjectUtils.notNull(jsonProperties.get());
186   }
187 
188   @Override
189   @Nullable
190   public String getFormalName() {
191     return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
192   }
193 
194   @Override
195   @Nullable
196   public MarkupLine getDescription() {
197     return ModelUtil.resolveToMarkupLine(getAnnotation().description());
198   }
199 
200   @Override
201   @NonNull
202   public String getName() {
203     return getAnnotation().name();
204   }
205 
206   @Override
207   @Nullable
208   public Integer getIndex() {
209     return ModelUtil.resolveDefaultInteger(getAnnotation().index());
210   }
211 
212   @Override
213   @Nullable
214   public MarkupMultiline getRemarks() {
215     return ModelUtil.resolveToMarkupMultiline(getAnnotation().description());
216   }
217 
218   protected class FieldValue
219       implements IBoundFieldValue {
220     @NonNull
221     private final Field javaField;
222     @NonNull
223     private final BoundFieldValue annotation;
224     @NonNull
225     private final IDataTypeAdapter<?> javaTypeAdapter;
226     @Nullable
227     private final Object defaultValue;
228 
229     /**
230      * Construct a new field value binding.
231      *
232      * @param javaField
233      *          the Java field the field value is bound to
234      * @param annotationClass
235      *          the field value binding annotation Java class
236      * @param bindingContext
237      *          the Metaschema binding context managing this class
238      */
239     protected FieldValue(
240         @NonNull Field javaField,
241         @NonNull Class<BoundFieldValue> annotationClass,
242         @NonNull IBindingContext bindingContext) {
243       this.javaField = javaField;
244       this.annotation = ModelUtil.getAnnotation(javaField, annotationClass);
245       this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
246           this.annotation.typeAdapter(),
247           bindingContext);
248       this.defaultValue = ModelUtil.resolveDefaultValue(this.annotation.defaultValue(), this.javaTypeAdapter);
249     }
250 
251     /**
252      * Get the bound Java field.
253      *
254      * @return the bound Java field
255      */
256     @Override
257     @NonNull
258     public Field getField() {
259       return javaField;
260     }
261 
262     /**
263      * Get the binding Java annotation.
264      *
265      * @return the binding Java annotation
266      */
267     @NonNull
268     public BoundFieldValue getAnnotation() {
269       return annotation;
270     }
271 
272     @Override
273     public IBoundDefinitionModelFieldComplex getParentFieldDefinition() {
274       return DefinitionField.this;
275     }
276 
277     @Override
278     public String getJsonValueKeyName() {
279       String name = ModelUtil.resolveNoneOrValue(getAnnotation().valueKeyName());
280       return name == null ? getJavaTypeAdapter().getDefaultJsonValueKey() : name;
281     }
282 
283     @Override
284     public String getJsonValueKeyFlagName() {
285       return ModelUtil.resolveNoneOrValue(getAnnotation().valueKeyName());
286     }
287 
288     @Override
289     public Object getDefaultValue() {
290       return defaultValue;
291     }
292 
293     @Override
294     public IDataTypeAdapter<?> getJavaTypeAdapter() {
295       return javaTypeAdapter;
296     }
297 
298     @Override
299     public Object getEffectiveDefaultValue() {
300       return getDefaultValue();
301     }
302 
303     @Override
304     public String getJsonName() {
305       return getEffectiveJsonValueKeyName();
306     }
307   }
308   // ----------------------------------------
309   // - End annotation driven code - CPD-OFF -
310   // ----------------------------------------
311 }