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