1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind.model;
7   
8   import java.io.IOException;
9   import java.util.Collection;
10  import java.util.Map;
11  import java.util.function.Function;
12  import java.util.function.Predicate;
13  import java.util.stream.Collectors;
14  import java.util.stream.Stream;
15  
16  import dev.metaschema.core.datatype.IDataTypeAdapter;
17  import dev.metaschema.core.model.IBoundObject;
18  import dev.metaschema.core.qname.IEnhancedQName;
19  import dev.metaschema.core.util.ObjectUtils;
20  import dev.metaschema.databind.model.info.IItemReadHandler;
21  import dev.metaschema.databind.model.info.IItemWriteHandler;
22  import edu.umd.cs.findbugs.annotations.NonNull;
23  import edu.umd.cs.findbugs.annotations.Nullable;
24  
25  /**
26   * Represents a field definition bound to a Java class.
27   * <p>
28   * This definition is considered "complex", since it is bound to a Java class.
29   */
30  public interface IBoundDefinitionModelFieldComplex
31      extends IBoundDefinitionModelField<IBoundObject>, IBoundDefinitionModelComplex {
32  
33    // Complex Field Definition Features
34    // =================================
35  
36    @Override
37    @NonNull
38    default IBoundDefinitionModelFieldComplex getDefinition() {
39      return this;
40    }
41  
42    @Override
43    default Object getDefaultValue() {
44      Object retval = null;
45      IBoundDefinitionModelFieldComplex definition = getDefinition();
46      IBoundFieldValue fieldValue = definition.getFieldValue();
47  
48      Object fieldValueDefault = fieldValue.getDefaultValue();
49      if (fieldValueDefault != null) {
50        retval = definition.newInstance(null);
51        fieldValue.setValue(retval, fieldValueDefault);
52  
53        // since the field value is non-null, populate the flags
54        for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
55          assert flag != null;
56  
57          Object flagDefault = flag.getResolvedDefaultValue();
58          if (flagDefault != null) {
59            flag.setValue(retval, flagDefault);
60          }
61        }
62      }
63      return retval;
64    }
65  
66    /**
67     * Get the bound field value associated with this field.
68     *
69     * @return the field's value binding
70     */
71    @NonNull
72    IBoundFieldValue getFieldValue();
73  
74    @Override
75    @NonNull
76    default Object getFieldValue(@NonNull Object item) {
77      return ObjectUtils.requireNonNull(getFieldValue().getValue(item));
78    }
79  
80    @Override
81    @NonNull
82    default String getJsonValueKeyName() {
83      return getFieldValue().getJsonValueKeyName();
84    }
85  
86    @Override
87    @NonNull
88    default IDataTypeAdapter<?> getJavaTypeAdapter() {
89      return getFieldValue().getJavaTypeAdapter();
90    }
91  
92    @SuppressWarnings("PMD.NullAssignment")
93    @Override
94    @NonNull
95    default Map<String, IBoundProperty<?>> getJsonProperties(@Nullable Predicate<IBoundInstanceFlag> flagFilter) {
96      Predicate<IBoundInstanceFlag> actualFlagFilter = flagFilter;
97  
98      IBoundFieldValue fieldValue = getFieldValue();
99      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 }