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