DefaultFieldClassBinding.java
/*
* Portions of this software was developed by employees of the National Institute
* of Standards and Technology (NIST), an agency of the Federal Government and is
* being made available as a public service. Pursuant to title 17 United States
* Code Section 105, works of NIST employees are not subject to copyright
* protection in the United States. This software may be subject to foreign
* copyright. Permission in the United States and in foreign countries, to the
* extent that NIST may hold copyright, to use, copy, modify, create derivative
* works, and distribute this software and its documentation without fee is hereby
* granted on a non-exclusive basis, provided that this notice and disclaimer
* of warranty appears in all copies.
*
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
*/
package gov.nist.secauto.metaschema.databind.model;
import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
import gov.nist.secauto.metaschema.core.model.IFlagContainerSupport;
import gov.nist.secauto.metaschema.core.model.IModule;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.InternalModelSource;
import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.IBindingContext;
import gov.nist.secauto.metaschema.databind.io.BindingException;
import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaFieldValue;
import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
import java.util.Objects;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import nl.talsmasoftware.lazy4j.Lazy;
public class DefaultFieldClassBinding
extends AbstractClassBinding
implements IFieldClassBinding {
@NonNull
private final MetaschemaField metaschemaField;
private IBoundFieldValueInstance fieldValue;
private IBoundFlagInstance jsonValueKeyFlagInstance;
private final Lazy<ClassBindingFlagContainerSupport> flagContainer;
private final Lazy<IValueConstrained> constraints;
/**
* Create a new {@link IClassBinding} for a Java bean annotated with the
* {@link BoundField} annotation.
*
* @param clazz
* the Java bean class
* @param bindingContext
* the Module binding environment context
* @return the Module field binding for the class
*/
@NonNull
public static DefaultFieldClassBinding createInstance(
@NonNull Class<?> clazz,
@NonNull IBindingContext bindingContext) {
Objects.requireNonNull(clazz, "clazz");
if (!clazz.isAnnotationPresent(MetaschemaField.class)) {
throw new IllegalArgumentException(
String.format("Class '%s' is missing the '%s' annotation.",
clazz.getName(),
MetaschemaField.class.getName()));
}
return new DefaultFieldClassBinding(clazz, bindingContext);
}
/**
* Construct a new {@link IClassBinding} for a Java bean annotated with the
* {@link BoundField} annotation.
*
* @param clazz
* the Java bean class
* @param bindingContext
* the class binding context for which this class is participating
*/
protected DefaultFieldClassBinding(
@NonNull Class<?> clazz,
@NonNull IBindingContext bindingContext) {
super(clazz, bindingContext);
this.metaschemaField = ObjectUtils.notNull(clazz.getAnnotation(MetaschemaField.class));
this.flagContainer = Lazy.lazy(() -> new ClassBindingFlagContainerSupport(this, this::handleFlagInstance));
this.constraints = Lazy.lazy(() -> new ValueConstraintSupport(
clazz.getAnnotation(ValueConstraints.class),
InternalModelSource.instance()));
}
@SuppressWarnings("null")
@Override
public IFlagContainerSupport<IBoundFlagInstance> getFlagContainer() {
return flagContainer.get();
}
@SuppressWarnings("null")
@Override
public IValueConstrained getConstraintSupport() {
return constraints.get();
}
@NonNull
private MetaschemaField getMetaschemaFieldAnnotation() {
return metaschemaField;
}
@Override
public String getFormalName() {
return ModelUtil.resolveToString(getMetaschemaFieldAnnotation().formalName());
}
@Override
public MarkupLine getDescription() {
return ModelUtil.resolveToMarkupLine(getMetaschemaFieldAnnotation().description());
}
@Override
public @Nullable MarkupMultiline getRemarks() {
return ModelUtil.resolveToMarkupMultiline(getMetaschemaFieldAnnotation().description());
}
@Override
public String getName() {
return getMetaschemaFieldAnnotation().name();
}
@Override
public Object getDefaultValue() {
return getFieldValueInstance().getDefaultValue();
}
/**
* Collect all fields that are part of the model for this class.
*
* @param clazz
* the class
* @return the field value instances if found or {@code null} otherwise
*/
protected java.lang.reflect.Field getFieldValueField(Class<?> clazz) {
java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
java.lang.reflect.Field retval = null;
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
// get instances from superclass
retval = getFieldValueField(superClass);
}
if (retval == null) {
for (java.lang.reflect.Field field : fields) {
if (!field.isAnnotationPresent(MetaschemaFieldValue.class)) {
// skip fields that aren't a field or assembly instance
continue;
}
if (field.isAnnotationPresent(Ignore.class)) {
// skip this field, since it is ignored
continue;
}
retval = field;
}
}
return retval;
}
/**
* Initialize the flag instances for this class.
*
* @return the field value instance
*/
protected IBoundFieldValueInstance initalizeFieldValueInstance() {
synchronized (this) {
if (this.fieldValue == null) {
java.lang.reflect.Field field = getFieldValueField(getBoundClass());
if (field == null) {
throw new IllegalArgumentException(
String.format("Class '%s' is missing the '%s' annotation on one of its fields.",
getBoundClass().getName(),
MetaschemaFieldValue.class.getName()));
}
this.fieldValue = new DefaultFieldValueProperty(this, field);
}
return this.fieldValue;
}
}
@Override
public boolean isInline() {
return false;
}
@Override
public IBoundFieldInstance getInlineInstance() {
return null;
}
@SuppressWarnings("null")
@Override
public IBoundFieldValueInstance getFieldValueInstance() {
return initalizeFieldValueInstance();
}
@Override
public Object getFieldValue(@NonNull Object item) {
return ObjectUtils.requireNonNull(getFieldValueInstance().getValue(item));
}
protected void handleFlagInstance(IBoundFlagInstance instance) {
if (instance.isJsonValueKey()) {
this.jsonValueKeyFlagInstance = instance;
}
}
@Override
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "access is restricted using interface")
public IBoundFlagInstance getJsonValueKeyFlagInstance() {
// lazy load flags
flagContainer.get();
return jsonValueKeyFlagInstance;
}
@Override
public String getJsonValueKeyName() {
return getFieldValueInstance().getJsonValueKeyName();
}
@Override
public IDataTypeAdapter<?> getJavaTypeAdapter() {
return getFieldValueInstance().getJavaTypeAdapter();
}
@Override
protected void copyBoundObjectInternal(@NonNull Object fromInstance, @NonNull Object toInstance)
throws BindingException {
super.copyBoundObjectInternal(fromInstance, toInstance);
getFieldValueInstance().copyBoundObject(fromInstance, toInstance);
}
@Override
protected Class<? extends IModule> getModuleClass() {
return getMetaschemaFieldAnnotation().moduleClass();
}
}