001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.model;
007
008import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
009import gov.nist.secauto.metaschema.core.model.IBoundObject;
010import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
011import gov.nist.secauto.metaschema.core.util.ObjectUtils;
012import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler;
013import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;
014
015import java.io.IOException;
016import java.util.Collection;
017import java.util.Map;
018import java.util.function.Function;
019import java.util.function.Predicate;
020import java.util.stream.Collectors;
021import java.util.stream.Stream;
022
023import edu.umd.cs.findbugs.annotations.NonNull;
024import edu.umd.cs.findbugs.annotations.Nullable;
025
026/**
027 * Represents a field definition bound to a Java class.
028 * <p>
029 * This definition is considered "complex", since it is bound to a Java class.
030 */
031public interface IBoundDefinitionModelFieldComplex
032    extends IBoundDefinitionModelField<IBoundObject>, IBoundDefinitionModelComplex {
033
034  // Complex Field Definition Features
035  // =================================
036
037  @Override
038  @NonNull
039  default IBoundDefinitionModelFieldComplex getDefinition() {
040    return this;
041  }
042
043  @Override
044  default Object getDefaultValue() {
045    Object retval = null;
046    IBoundDefinitionModelFieldComplex definition = getDefinition();
047    IBoundFieldValue fieldValue = definition.getFieldValue();
048
049    Object fieldValueDefault = fieldValue.getDefaultValue();
050    if (fieldValueDefault != null) {
051      retval = definition.newInstance(null);
052      fieldValue.setValue(retval, fieldValueDefault);
053
054      // since the field value is non-null, populate the flags
055      for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
056        assert flag != null;
057
058        Object flagDefault = flag.getResolvedDefaultValue();
059        if (flagDefault != null) {
060          flag.setValue(retval, flagDefault);
061        }
062      }
063    }
064    return retval;
065  }
066
067  /**
068   * Get the bound field value associated with this field.
069   *
070   * @return the field's value binding
071   */
072  @NonNull
073  IBoundFieldValue getFieldValue();
074
075  @Override
076  @NonNull
077  default Object getFieldValue(@NonNull Object item) {
078    return ObjectUtils.requireNonNull(getFieldValue().getValue(item));
079  }
080
081  @Override
082  @NonNull
083  default String getJsonValueKeyName() {
084    return getFieldValue().getJsonValueKeyName();
085  }
086
087  @Override
088  @NonNull
089  default IDataTypeAdapter<?> getJavaTypeAdapter() {
090    return getFieldValue().getJavaTypeAdapter();
091  }
092
093  @SuppressWarnings("PMD.NullAssignment")
094  @Override
095  @NonNull
096  default Map<String, IBoundProperty<?>> getJsonProperties(@Nullable Predicate<IBoundInstanceFlag> flagFilter) {
097    Predicate<IBoundInstanceFlag> actualFlagFilter = flagFilter;
098
099    IBoundFieldValue fieldValue = getFieldValue();
100    IBoundInstanceFlag jsonValueKey = getDefinition().getJsonValueKeyFlagInstance();
101    if (jsonValueKey != null) {
102      Predicate<IBoundInstanceFlag> jsonValueKeyFilter = flag -> !flag.equals(jsonValueKey);
103      actualFlagFilter = actualFlagFilter == null ? jsonValueKeyFilter : actualFlagFilter.and(jsonValueKeyFilter);
104      // ensure the field value is omitted too!
105      fieldValue = null;
106    }
107
108    Stream<? extends IBoundInstanceFlag> flagStream = getFlagInstances().stream();
109    if (actualFlagFilter != null) {
110      flagStream = flagStream.filter(actualFlagFilter);
111    }
112
113    if (fieldValue != null) {
114      // determine if we use the field value or not
115      Collection<? extends IBoundInstanceFlag> flagInstances = flagStream
116          .collect(Collectors.toList());
117
118      if (flagInstances.isEmpty()) {
119        // no relevant flags, so this field should expect a scalar value
120        fieldValue = null;
121      }
122      flagStream = flagInstances.stream();
123    }
124
125    Stream<? extends IBoundProperty<?>> resultStream = fieldValue == null
126        ? flagStream
127        : Stream.concat(flagStream, Stream.of(getFieldValue()));
128
129    return ObjectUtils.notNull(resultStream
130        .collect(Collectors.toUnmodifiableMap(IBoundProperty::getJsonName, Function.identity())));
131  }
132
133  @Override
134  @NonNull
135  default IBoundObject readItem(IBoundObject parent, IItemReadHandler handler) throws IOException {
136    return handler.readItemField(parent, this);
137  }
138
139  @Override
140  default void writeItem(IBoundObject item, IItemWriteHandler handler) throws IOException {
141    handler.writeItemField(item, this);
142  }
143
144  @Override
145  default boolean canHandleXmlQName(IEnhancedQName qname) {
146    // not handled, since not root
147    return false;
148  }
149}