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