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    @Override
93    @NonNull
94    default Map<String, IBoundProperty<?>> getJsonProperties(@Nullable Predicate<IBoundInstanceFlag> flagFilter) {
95      Predicate<IBoundInstanceFlag> actualFlagFilter = flagFilter;
96  
97      IBoundFieldValue fieldValue = getFieldValue();
98      IBoundInstanceFlag jsonValueKey = getDefinition().getJsonValueKeyFlagInstance();
99      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 }