1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model;
7   
8   import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
9   import gov.nist.secauto.metaschema.core.model.IBoundObject;
10  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
11  import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler;
12  import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;
13  
14  import java.io.IOException;
15  import java.util.Collection;
16  import java.util.Map;
17  import java.util.function.Function;
18  import java.util.function.Predicate;
19  import java.util.stream.Collectors;
20  import java.util.stream.Stream;
21  
22  import javax.xml.namespace.QName;
23  
24  import edu.umd.cs.findbugs.annotations.NonNull;
25  import edu.umd.cs.findbugs.annotations.Nullable;
26  
27  /**
28   * Represents a field definition bound to a Java class.
29   * <p>
30   * This definition is considered "complex", since it is bound to a Java class.
31   */
32  public interface IBoundDefinitionModelFieldComplex
33      extends IBoundDefinitionModelField<IBoundObject>, IBoundDefinitionModelComplex {
34  
35    // Complex Field Definition Features
36    // =================================
37  
38    @Override
39    @NonNull
40    default IBoundDefinitionModelFieldComplex getDefinition() {
41      return this;
42    }
43  
44    @Override
45    default Object getDefaultValue() {
46      Object retval = null;
47      IBoundDefinitionModelFieldComplex definition = getDefinition();
48      IBoundFieldValue fieldValue = definition.getFieldValue();
49  
50      Object fieldValueDefault = fieldValue.getDefaultValue();
51      if (fieldValueDefault != null) {
52        retval = definition.newInstance(null);
53        fieldValue.setValue(retval, fieldValueDefault);
54  
55        // since the field value is non-null, populate the flags
56        for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
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(QName qname) {
145     // not handled, since not root
146     return false;
147   }
148 }