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          assert flag != null;
57  
58          Object flagDefault = flag.getResolvedDefaultValue();
59          if (flagDefault != null) {
60            flag.setValue(retval, flagDefault);
61          }
62        }
63      }
64      return retval;
65    }
66  
67    /**
68     * Get the bound field value associated with this field.
69     *
70     * @return the field's value binding
71     */
72    @NonNull
73    IBoundFieldValue getFieldValue();
74  
75    @Override
76    @NonNull
77    default Object getFieldValue(@NonNull Object item) {
78      return ObjectUtils.requireNonNull(getFieldValue().getValue(item));
79    }
80  
81    @Override
82    @NonNull
83    default String getJsonValueKeyName() {
84      return getFieldValue().getJsonValueKeyName();
85    }
86  
87    @Override
88    @NonNull
89    default IDataTypeAdapter<?> getJavaTypeAdapter() {
90      return getFieldValue().getJavaTypeAdapter();
91    }
92  
93    @SuppressWarnings("PMD.NullAssignment")
94    @Override
95    @NonNull
96    default Map<String, IBoundProperty<?>> getJsonProperties(@Nullable Predicate<IBoundInstanceFlag> flagFilter) {
97      Predicate<IBoundInstanceFlag> actualFlagFilter = flagFilter;
98  
99      IBoundFieldValue fieldValue = getFieldValue();
100     IBoundInstanceFlag jsonValueKey = getDefinition().getJsonValueKeyFlagInstance();
101     if (jsonValueKey != null) {
102       Predicate<IBoundInstanceFlag> jsonValueKeyFilter = flag -> !flag.equals(jsonValueKey);
103       actualFlagFilter = actualFlagFilter == null ? jsonValueKeyFilter : actualFlagFilter.and(jsonValueKeyFilter);
104       // ensure the field value is omitted too!
105       fieldValue = null;
106     }
107 
108     Stream<? extends IBoundInstanceFlag> flagStream = getFlagInstances().stream();
109     if (actualFlagFilter != null) {
110       flagStream = flagStream.filter(actualFlagFilter);
111     }
112 
113     if (fieldValue != null) {
114       // determine if we use the field value or not
115       Collection<? extends IBoundInstanceFlag> flagInstances = flagStream
116           .collect(Collectors.toList());
117 
118       if (flagInstances.isEmpty()) {
119         // no relevant flags, so this field should expect a scalar value
120         fieldValue = null;
121       }
122       flagStream = flagInstances.stream();
123     }
124 
125     Stream<? extends IBoundProperty<?>> resultStream = fieldValue == null
126         ? flagStream
127         : Stream.concat(flagStream, Stream.of(getFieldValue()));
128 
129     return ObjectUtils.notNull(resultStream
130         .collect(Collectors.toUnmodifiableMap(IBoundProperty::getJsonName, Function.identity())));
131   }
132 
133   @Override
134   @NonNull
135   default IBoundObject readItem(IBoundObject parent, IItemReadHandler handler) throws IOException {
136     return handler.readItemField(parent, this);
137   }
138 
139   @Override
140   default void writeItem(IBoundObject item, IItemWriteHandler handler) throws IOException {
141     handler.writeItemField(item, this);
142   }
143 
144   @Override
145   default boolean canHandleXmlQName(IEnhancedQName qname) {
146     // not handled, since not root
147     return false;
148   }
149 }