001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.databind.model;
028
029import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
030import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
031import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
032import gov.nist.secauto.metaschema.core.model.IFlagContainerSupport;
033import gov.nist.secauto.metaschema.core.model.IModule;
034import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.InternalModelSource;
035import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
036import gov.nist.secauto.metaschema.core.util.ObjectUtils;
037import gov.nist.secauto.metaschema.databind.IBindingContext;
038import gov.nist.secauto.metaschema.databind.io.BindingException;
039import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
040import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
041import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
042import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaFieldValue;
043import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
044
045import java.util.Objects;
046
047import edu.umd.cs.findbugs.annotations.NonNull;
048import edu.umd.cs.findbugs.annotations.Nullable;
049import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
050import nl.talsmasoftware.lazy4j.Lazy;
051
052public class DefaultFieldClassBinding
053    extends AbstractClassBinding
054    implements IFieldClassBinding {
055
056  @NonNull
057  private final MetaschemaField metaschemaField;
058  private IBoundFieldValueInstance fieldValue;
059  private IBoundFlagInstance jsonValueKeyFlagInstance;
060  private final Lazy<ClassBindingFlagContainerSupport> flagContainer;
061  private final Lazy<IValueConstrained> constraints;
062
063  /**
064   * Create a new {@link IClassBinding} for a Java bean annotated with the
065   * {@link BoundField} annotation.
066   *
067   * @param clazz
068   *          the Java bean class
069   * @param bindingContext
070   *          the Module binding environment context
071   * @return the Module field binding for the class
072   */
073  @NonNull
074  public static DefaultFieldClassBinding createInstance(
075      @NonNull Class<?> clazz,
076      @NonNull IBindingContext bindingContext) {
077    Objects.requireNonNull(clazz, "clazz");
078    if (!clazz.isAnnotationPresent(MetaschemaField.class)) {
079      throw new IllegalArgumentException(
080          String.format("Class '%s' is missing the '%s' annotation.",
081              clazz.getName(),
082              MetaschemaField.class.getName()));
083    }
084    return new DefaultFieldClassBinding(clazz, bindingContext);
085  }
086
087  /**
088   * Construct a new {@link IClassBinding} for a Java bean annotated with the
089   * {@link BoundField} annotation.
090   *
091   * @param clazz
092   *          the Java bean class
093   * @param bindingContext
094   *          the class binding context for which this class is participating
095   */
096  protected DefaultFieldClassBinding(
097      @NonNull Class<?> clazz,
098      @NonNull IBindingContext bindingContext) {
099    super(clazz, bindingContext);
100    this.metaschemaField = ObjectUtils.notNull(clazz.getAnnotation(MetaschemaField.class));
101    this.flagContainer = Lazy.lazy(() -> new ClassBindingFlagContainerSupport(this, this::handleFlagInstance));
102    this.constraints = Lazy.lazy(() -> new ValueConstraintSupport(
103        clazz.getAnnotation(ValueConstraints.class),
104        InternalModelSource.instance()));
105  }
106
107  @SuppressWarnings("null")
108  @Override
109  public IFlagContainerSupport<IBoundFlagInstance> getFlagContainer() {
110    return flagContainer.get();
111  }
112
113  @SuppressWarnings("null")
114  @Override
115  public IValueConstrained getConstraintSupport() {
116    return constraints.get();
117  }
118
119  @NonNull
120  private MetaschemaField getMetaschemaFieldAnnotation() {
121    return metaschemaField;
122  }
123
124  @Override
125  public String getFormalName() {
126    return ModelUtil.resolveToString(getMetaschemaFieldAnnotation().formalName());
127  }
128
129  @Override
130  public MarkupLine getDescription() {
131    return ModelUtil.resolveToMarkupLine(getMetaschemaFieldAnnotation().description());
132  }
133
134  @Override
135  public @Nullable MarkupMultiline getRemarks() {
136    return ModelUtil.resolveToMarkupMultiline(getMetaschemaFieldAnnotation().description());
137  }
138
139  @Override
140  public String getName() {
141    return getMetaschemaFieldAnnotation().name();
142  }
143
144  @Override
145  public Object getDefaultValue() {
146    return getFieldValueInstance().getDefaultValue();
147  }
148
149  /**
150   * Collect all fields that are part of the model for this class.
151   *
152   * @param clazz
153   *          the class
154   * @return the field value instances if found or {@code null} otherwise
155   */
156  protected java.lang.reflect.Field getFieldValueField(Class<?> clazz) {
157    java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
158
159    java.lang.reflect.Field retval = null;
160
161    Class<?> superClass = clazz.getSuperclass();
162    if (superClass != null) {
163      // get instances from superclass
164      retval = getFieldValueField(superClass);
165    }
166
167    if (retval == null) {
168      for (java.lang.reflect.Field field : fields) {
169        if (!field.isAnnotationPresent(MetaschemaFieldValue.class)) {
170          // skip fields that aren't a field or assembly instance
171          continue;
172        }
173
174        if (field.isAnnotationPresent(Ignore.class)) {
175          // skip this field, since it is ignored
176          continue;
177        }
178        retval = field;
179      }
180    }
181    return retval;
182  }
183
184  /**
185   * Initialize the flag instances for this class.
186   *
187   * @return the field value instance
188   */
189  protected IBoundFieldValueInstance initalizeFieldValueInstance() {
190    synchronized (this) {
191      if (this.fieldValue == null) {
192        java.lang.reflect.Field field = getFieldValueField(getBoundClass());
193        if (field == null) {
194          throw new IllegalArgumentException(
195              String.format("Class '%s' is missing the '%s' annotation on one of its fields.",
196                  getBoundClass().getName(),
197                  MetaschemaFieldValue.class.getName()));
198        }
199
200        this.fieldValue = new DefaultFieldValueProperty(this, field);
201      }
202      return this.fieldValue;
203    }
204  }
205
206  @Override
207  public boolean isInline() {
208    return false;
209  }
210
211  @Override
212  public IBoundFieldInstance getInlineInstance() {
213    return null;
214  }
215
216  @SuppressWarnings("null")
217  @Override
218  public IBoundFieldValueInstance getFieldValueInstance() {
219    return initalizeFieldValueInstance();
220  }
221
222  @Override
223  public Object getFieldValue(@NonNull Object item) {
224    return ObjectUtils.requireNonNull(getFieldValueInstance().getValue(item));
225  }
226
227  protected void handleFlagInstance(IBoundFlagInstance instance) {
228    if (instance.isJsonValueKey()) {
229      this.jsonValueKeyFlagInstance = instance;
230    }
231  }
232
233  @Override
234  @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "access is restricted using interface")
235  public IBoundFlagInstance getJsonValueKeyFlagInstance() {
236    // lazy load flags
237    flagContainer.get();
238    return jsonValueKeyFlagInstance;
239  }
240
241  @Override
242  public String getJsonValueKeyName() {
243    return getFieldValueInstance().getJsonValueKeyName();
244  }
245
246  @Override
247  public IDataTypeAdapter<?> getJavaTypeAdapter() {
248    return getFieldValueInstance().getJavaTypeAdapter();
249  }
250
251  @Override
252  protected void copyBoundObjectInternal(@NonNull Object fromInstance, @NonNull Object toInstance)
253      throws BindingException {
254    super.copyBoundObjectInternal(fromInstance, toInstance);
255
256    getFieldValueInstance().copyBoundObject(fromInstance, toInstance);
257  }
258
259  @Override
260  protected Class<? extends IModule> getModuleClass() {
261    return getMetaschemaFieldAnnotation().moduleClass();
262  }
263}