IBoundDefinitionModelFieldComplex.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.databind.model;

import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.model.IBoundObject;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler;
import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.namespace.QName;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

/**
 * Represents a field definition bound to a Java class.
 * <p>
 * This definition is considered "complex", since it is bound to a Java class.
 */
public interface IBoundDefinitionModelFieldComplex
    extends IBoundDefinitionModelField<IBoundObject>, IBoundDefinitionModelComplex {

  // Complex Field Definition Features
  // =================================

  @Override
  @NonNull
  default IBoundDefinitionModelFieldComplex getDefinition() {
    return this;
  }

  @Override
  default Object getDefaultValue() {
    Object retval = null;
    IBoundDefinitionModelFieldComplex definition = getDefinition();
    IBoundFieldValue fieldValue = definition.getFieldValue();

    Object fieldValueDefault = fieldValue.getDefaultValue();
    if (fieldValueDefault != null) {
      retval = definition.newInstance(null);
      fieldValue.setValue(retval, fieldValueDefault);

      // since the field value is non-null, populate the flags
      for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
        Object flagDefault = flag.getResolvedDefaultValue();
        if (flagDefault != null) {
          flag.setValue(retval, flagDefault);
        }
      }
    }
    return retval;
  }

  /**
   * Get the bound field value associated with this field.
   *
   * @return the field's value binding
   */
  @NonNull
  IBoundFieldValue getFieldValue();

  @Override
  @NonNull
  default Object getFieldValue(@NonNull Object item) {
    return ObjectUtils.requireNonNull(getFieldValue().getValue(item));
  }

  @Override
  @NonNull
  default String getJsonValueKeyName() {
    return getFieldValue().getJsonValueKeyName();
  }

  @Override
  @NonNull
  default IDataTypeAdapter<?> getJavaTypeAdapter() {
    return getFieldValue().getJavaTypeAdapter();
  }

  @SuppressWarnings("PMD.NullAssignment")
  @Override
  @NonNull
  default Map<String, IBoundProperty<?>> getJsonProperties(@Nullable Predicate<IBoundInstanceFlag> flagFilter) {
    Predicate<IBoundInstanceFlag> actualFlagFilter = flagFilter;

    IBoundFieldValue fieldValue = getFieldValue();
    IBoundInstanceFlag jsonValueKey = getDefinition().getJsonValueKeyFlagInstance();
    if (jsonValueKey != null) {
      Predicate<IBoundInstanceFlag> jsonValueKeyFilter = flag -> !flag.equals(jsonValueKey);
      actualFlagFilter = actualFlagFilter == null ? jsonValueKeyFilter : actualFlagFilter.and(jsonValueKeyFilter);
      // ensure the field value is omitted too!
      fieldValue = null;
    }

    Stream<? extends IBoundInstanceFlag> flagStream = getFlagInstances().stream();
    if (actualFlagFilter != null) {
      flagStream = flagStream.filter(actualFlagFilter);
    }

    if (fieldValue != null) {
      // determine if we use the field value or not
      Collection<? extends IBoundInstanceFlag> flagInstances = flagStream
          .collect(Collectors.toList());

      if (flagInstances.isEmpty()) {
        // no relevant flags, so this field should expect a scalar value
        fieldValue = null;
      }
      flagStream = flagInstances.stream();
    }

    Stream<? extends IBoundProperty<?>> resultStream = fieldValue == null
        ? flagStream
        : Stream.concat(flagStream, Stream.of(getFieldValue()));

    return ObjectUtils.notNull(resultStream
        .collect(Collectors.toUnmodifiableMap(IBoundProperty::getJsonName, Function.identity())));
  }

  @Override
  @NonNull
  default IBoundObject readItem(IBoundObject parent, IItemReadHandler handler) throws IOException {
    return handler.readItemField(parent, this);
  }

  @Override
  default void writeItem(IBoundObject item, IItemWriteHandler handler) throws IOException {
    handler.writeItemField(item, this);
  }

  @Override
  default boolean canHandleXmlQName(QName qname) {
    // not handled, since not root
    return false;
  }
}