1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.schemagen.json.impl.builder;
7   
8   import com.fasterxml.jackson.databind.node.ArrayNode;
9   import com.fasterxml.jackson.databind.node.ObjectNode;
10  
11  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
12  import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
13  import gov.nist.secauto.metaschema.core.model.IFlagInstance;
14  import gov.nist.secauto.metaschema.core.model.IGroupable;
15  import gov.nist.secauto.metaschema.core.model.IModelDefinition;
16  import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
17  import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute;
18  import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped;
19  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
20  import gov.nist.secauto.metaschema.schemagen.json.IDataTypeJsonSchema;
21  import gov.nist.secauto.metaschema.schemagen.json.IDefineableJsonSchema.IKey;
22  import gov.nist.secauto.metaschema.schemagen.json.IDefinitionJsonSchema;
23  import gov.nist.secauto.metaschema.schemagen.json.IJsonGenerationState;
24  
25  import java.util.LinkedList;
26  import java.util.List;
27  
28  import edu.umd.cs.findbugs.annotations.NonNull;
29  import edu.umd.cs.findbugs.annotations.Nullable;
30  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
31  
32  public abstract class AbstractCollectionBuilder<T extends AbstractCollectionBuilder<T>>
33      extends AbstractBuilder<T>
34      implements IModelInstanceBuilder<T> {
35    private int minOccurrence = IGroupable.DEFAULT_GROUP_AS_MIN_OCCURS;
36    private int maxOccurrence = IGroupable.DEFAULT_GROUP_AS_MAX_OCCURS;
37  
38    @NonNull
39    private final List<IModelInstanceBuilder.IType> types = new LinkedList<>();
40  
41    @Override
42    public T addItemType(INamedModelInstanceAbsolute itemType) {
43      types.add(new AbsoluteType(itemType));
44      return thisBuilder();
45    }
46  
47    @Override
48    public T addItemType(INamedModelInstanceGrouped itemType) {
49      types.add(new GroupedType(itemType));
50      return thisBuilder();
51    }
52  
53    @Override
54    public List<IType> getTypes() {
55      return CollectionUtil.unmodifiableList(types);
56    }
57  
58    @Override
59    public T minItems(int min) {
60      if (min < 0) {
61        throw new IllegalArgumentException(
62            String.format("The minimum value '%d' cannot be negative.", min));
63      }
64      minOccurrence = min;
65      return thisBuilder();
66    }
67  
68    @Override
69    public T maxItems(int max) {
70      if (max < -1 || max == 0) {
71        throw new IllegalArgumentException(
72            String.format("The maximum value '%d' must be -1 or a positive value.", max));
73      }
74      maxOccurrence = max;
75      return thisBuilder();
76    }
77  
78    @Override
79    public int getMinOccurrence() {
80      return minOccurrence;
81    }
82  
83    @Override
84    public int getMaxOccurrence() {
85      return maxOccurrence;
86    }
87  
88    /**
89     * Generates the type reference(s).
90     *
91     * @param object
92     *          the parent object node to add properties to
93     * @param state
94     *          the generation state
95     */
96    protected void buildInternal(
97        @NonNull ObjectNode object,
98        @NonNull IJsonGenerationState state) {
99      if (types.size() == 1) {
100       // build the item type reference
101       types.iterator().next().build(object, state);
102     } else if (types.size() > 1) {
103       // build an anyOf of the item type references
104       ArrayNode anyOf = object.putArray("anyOf");
105       for (IType type : types) {
106         type.build(anyOf, state);
107       }
108     }
109   }
110 
111   private abstract static class Type<T extends INamedModelInstance> implements IModelInstanceBuilder.IType {
112     @NonNull
113     private final T namedModelInstance;
114     @Nullable
115     private final IFlagInstance jsonKeyFlag;
116     @Nullable
117     private final String discriminatorProperty;
118     @Nullable
119     private final String discriminatorValue;
120     @NonNull
121     private final IKey key;
122 
123     @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
124     protected Type(@NonNull T instance) {
125       this.namedModelInstance = instance;
126 
127       this.jsonKeyFlag = instance.getEffectiveJsonKey();
128 
129       if (instance instanceof INamedModelInstanceGrouped) {
130         INamedModelInstanceGrouped grouped = (INamedModelInstanceGrouped) instance;
131         this.discriminatorProperty = grouped.getParentContainer().getJsonDiscriminatorProperty();
132         this.discriminatorValue = grouped.getEffectiveDisciminatorValue();
133       } else {
134         this.discriminatorProperty = null;
135         this.discriminatorValue = null;
136       }
137       this.key
138           = IKey.of(
139               instance.getDefinition(),
140               jsonKeyFlag == null ? null : jsonKeyFlag.getName(),
141               this.discriminatorProperty,
142               this.discriminatorValue);
143     }
144 
145     @NonNull
146     protected T getNamedModelInstance() {
147       return namedModelInstance;
148     }
149 
150     @Nullable
151     protected IFlagInstance getJsonKeyFlag() {
152       return jsonKeyFlag;
153     }
154 
155     @Nullable
156     protected String getJsonKeyFlagName() {
157       return jsonKeyFlag == null ? null : jsonKeyFlag.getEffectiveName();
158     }
159 
160     @Nullable
161     protected String getDiscriminatorProperty() {
162       return discriminatorProperty;
163     }
164 
165     @Nullable
166     protected String getDiscriminatorValue() {
167       return discriminatorValue;
168     }
169 
170     @Override
171     public IDefinitionJsonSchema<IFlagDefinition> getJsonKeyFlagSchema(@NonNull IJsonGenerationState state) {
172       IFlagInstance jsonKey = getJsonKeyFlag();
173       return jsonKey == null ? null : state.getSchema(IKey.of(jsonKey.getDefinition()));
174     }
175 
176     @Override
177     public IDataTypeJsonSchema getJsonKeyDataTypeSchema(IJsonGenerationState state) {
178       IFlagInstance jsonKey = getJsonKeyFlag();
179       return jsonKey == null ? null : state.getDataTypeSchemaForDefinition(jsonKey.getDefinition());
180     }
181 
182     @Override
183     public IDefinitionJsonSchema<IModelDefinition> getJsonSchema(IJsonGenerationState state) {
184       return state.getSchema(key);
185     }
186 
187     @Override
188     public void build(
189         @NonNull ArrayNode anyOf,
190         @NonNull IJsonGenerationState state) {
191       build(anyOf.addObject(), state);
192     }
193 
194     @Override
195     public void build(
196         @NonNull ObjectNode object,
197         @NonNull IJsonGenerationState state) {
198 
199       IModelDefinition definition = getNamedModelInstance().getDefinition();
200 
201       int flagCount = definition.getFlagInstances().size();
202       if (jsonKeyFlag != null) {
203         --flagCount;
204       }
205 
206       if (flagCount > 0) {
207         IDefinitionJsonSchema<IModelDefinition> schema = getJsonSchema(state);
208         schema.generateSchemaOrRef(object, state);
209       } else if (definition instanceof IFieldDefinition) {
210         IDataTypeJsonSchema schema = state.getSchema(((IFieldDefinition) definition).getJavaTypeAdapter());
211         schema.generateSchemaOrRef(object, state);
212       }
213     }
214   }
215 
216   private static final class AbsoluteType
217       extends Type<INamedModelInstanceAbsolute> {
218 
219     private AbsoluteType(@NonNull INamedModelInstanceAbsolute namedModelInstance) {
220       super(namedModelInstance);
221     }
222   }
223 
224   private static final class GroupedType
225       extends Type<INamedModelInstanceGrouped> {
226 
227     private GroupedType(@NonNull INamedModelInstanceGrouped namedModelInstance) {
228       super(namedModelInstance);
229     }
230   }
231 }