1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.schemagen.json;
7   
8   import com.fasterxml.jackson.databind.node.ObjectNode;
9   
10  import gov.nist.secauto.metaschema.core.model.IDefinition;
11  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
12  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
13  import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractDefinitionJsonSchema.SimpleKey;
14  import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractModelDefinitionJsonSchema.ComplexKey;
15  
16  import java.util.Comparator;
17  
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  /**
22   * Represents a JSON schema that is a global definition or an inline schema.
23   * <p>
24   * A schema of this type will be a global definition if
25   * {@link #isInline(IJsonGenerationState)} is {@code false}.
26   */
27  public interface IDefineableJsonSchema {
28  
29    /**
30     * Determine if the schema is defined inline or as a global definition.
31     *
32     * @param state
33     *          the schema generation state used for context
34     * @return {@code true} if the schema is to be defined inline or {@code false}
35     *         if the schema is to be defined globally
36     */
37    boolean isInline(@NonNull IJsonGenerationState state);
38  
39    default void generateSchemaOrRef(
40        @NonNull ObjectNode obj,
41        @NonNull IJsonGenerationState state) {
42      if (isInline(state)) {
43        generateInlineSchema(obj, state);
44      } else {
45        generateRef(obj, state);
46      }
47    }
48  
49    /**
50     * Generate the schema within the provided JSON object node.
51     *
52     * @param obj
53     *          the JSON object to populate
54     * @param state
55     *          the schema generation state used for context and writing
56     * @throws SchemaGenerationException
57     *           if an error occurred while writing the type
58     */
59    void generateInlineSchema(
60        @NonNull ObjectNode obj,
61        @NonNull IJsonGenerationState state);
62  
63    /**
64     * Get the definition's name.
65     *
66     * @param state
67     *          the schema generation state used for context and writing
68     * @return the definition name
69     * @throws IllegalStateException
70     *           if the JSON schema object is not a definition
71     */
72    @NonNull
73    String getDefinitionName(@NonNull IJsonGenerationState state);
74  
75    /**
76     * Get the definition's reference URI.
77     *
78     * @param state
79     *          the schema generation state used for context and writing
80     * @return the definition's reference URI
81     * @throws IllegalStateException
82     *           if the JSON schema object is not a definition
83     */
84    default String getDefinitionRef(@NonNull IJsonGenerationState state) {
85      return ObjectUtils.notNull(new StringBuilder()
86          .append("#/definitions/")
87          .append(getDefinitionName(state))
88          .toString());
89    }
90  
91    /**
92     * Generate a reference to a globally defined schema, within the provided JSON
93     * object node.
94     *
95     * @param obj
96     *          the JSON object to populate
97     * @param state
98     *          the schema generation state used for context and writing
99     * @throws SchemaGenerationException
100    *           if an error occurred while writing the type
101    */
102   default void generateRef(
103       @NonNull ObjectNode obj,
104       @NonNull IJsonGenerationState state) {
105     obj.put("$ref", getDefinitionRef(state));
106   }
107 
108   // /**
109   // * Determine if the JSON schema object is a JSON definition.
110   // *
111   // * @param state
112   // * the schema generation state used for context and writing
113   // * @return {@code true} if the SON schema object is a definition or
114   // * {@code false} otherwise
115   // */
116   // default boolean isDefinition(@NonNull IJsonGenerationState state) {
117   // return !isInline(state);
118   // }
119 
120   // REFACTOR: move to abstract implementation?
121   default void generateDefinition(@NonNull IJsonGenerationState state, @NonNull ObjectNode definitionsObject) {
122 
123     // create the definition property
124     ObjectNode definitionObj = ObjectUtils.notNull(
125         definitionsObject.putObject(getDefinitionName(state)));
126 
127     // Add identifier, see usnistgov/metaschema#160
128     definitionObj.put("$id", getDefinitionRef(state));
129 
130     // generate the definition object contents
131     generateInlineSchema(definitionObj, state);
132   }
133 
134   @SuppressWarnings("PMD.ShortClassName")
135   interface IKey extends Comparable<IKey> {
136     Comparator<IKey> KEY_COMPARATOR = Comparator
137         .<IKey, String>comparing(key -> key.getDefinition().getContainingModule().getShortName())
138         .thenComparing(key -> key.getDefinition().getEffectiveName())
139         .thenComparing(IKey::getJsonKeyFlagName, Comparator.nullsFirst(Comparator.naturalOrder()))
140         .thenComparing(IKey::getDiscriminatorProperty, Comparator.nullsFirst(Comparator.naturalOrder()))
141         .thenComparing(IKey::getDiscriminatorValue, Comparator.nullsFirst(Comparator.naturalOrder()));
142 
143     @SuppressWarnings("PMD.ShortMethodName")
144     @NonNull
145     static IKey of(@NonNull IDefinition definition) {
146       return new SimpleKey(definition);
147     }
148 
149     @SuppressWarnings("PMD.ShortMethodName")
150     @NonNull
151     static IKey of(
152         @NonNull IDefinition definition,
153         @Nullable String jsonKeyFlagName,
154         @Nullable String discriminatorProperty,
155         @Nullable String discriminatorValue) {
156       IKey retval;
157       if (jsonKeyFlagName != null || discriminatorProperty != null || discriminatorValue != null) {
158         retval = new ComplexKey(definition, jsonKeyFlagName, discriminatorProperty, discriminatorValue);
159       } else {
160         retval = of(definition);
161       }
162       return retval;
163     }
164 
165     @NonNull
166     IDefinition getDefinition();
167 
168     @Nullable
169     String getJsonKeyFlagName();
170 
171     @Nullable
172     String getDiscriminatorProperty();
173 
174     @Nullable
175     String getDiscriminatorValue();
176 
177     @Override
178     default int compareTo(IKey other) {
179       return KEY_COMPARATOR.compare(this, other);
180     }
181   }
182 }