001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.schemagen.json;
007
008import com.fasterxml.jackson.databind.node.ObjectNode;
009
010import gov.nist.secauto.metaschema.core.model.IDefinition;
011import gov.nist.secauto.metaschema.core.util.ObjectUtils;
012import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
013import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractDefinitionJsonSchema.SimpleKey;
014import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractModelDefinitionJsonSchema.ComplexKey;
015
016import java.util.Comparator;
017
018import edu.umd.cs.findbugs.annotations.NonNull;
019import edu.umd.cs.findbugs.annotations.Nullable;
020
021/**
022 * Represents a JSON schema that is a global definition or an inline schema.
023 * <p>
024 * A schema of this type will be a global definition if
025 * {@link #isInline(IJsonGenerationState)} is {@code false}.
026 */
027public interface IDefineableJsonSchema {
028
029  /**
030   * Determine if the schema is defined inline or as a global definition.
031   *
032   * @param state
033   *          the schema generation state used for context
034   * @return {@code true} if the schema is to be defined inline or {@code false}
035   *         if the schema is to be defined globally
036   */
037  boolean isInline(@NonNull IJsonGenerationState state);
038
039  default void generateSchemaOrRef(
040      @NonNull ObjectNode obj,
041      @NonNull IJsonGenerationState state) {
042    if (isInline(state)) {
043      generateInlineSchema(obj, state);
044    } else {
045      generateRef(obj, state);
046    }
047  }
048
049  /**
050   * Generate the schema within the provided JSON object node.
051   *
052   * @param obj
053   *          the JSON object to populate
054   * @param state
055   *          the schema generation state used for context and writing
056   * @throws SchemaGenerationException
057   *           if an error occurred while writing the type
058   */
059  void generateInlineSchema(
060      @NonNull ObjectNode obj,
061      @NonNull IJsonGenerationState state);
062
063  /**
064   * Get the definition's name.
065   *
066   * @param state
067   *          the schema generation state used for context and writing
068   * @return the definition name
069   * @throws IllegalStateException
070   *           if the JSON schema object is not a definition
071   */
072  @NonNull
073  String getDefinitionName(@NonNull IJsonGenerationState state);
074
075  /**
076   * Get the definition's reference URI.
077   *
078   * @param state
079   *          the schema generation state used for context and writing
080   * @return the definition's reference URI
081   * @throws IllegalStateException
082   *           if the JSON schema object is not a definition
083   */
084  default String getDefinitionRef(@NonNull IJsonGenerationState state) {
085    return ObjectUtils.notNull(new StringBuilder()
086        .append("#/definitions/")
087        .append(getDefinitionName(state))
088        .toString());
089  }
090
091  /**
092   * Generate a reference to a globally defined schema, within the provided JSON
093   * object node.
094   *
095   * @param obj
096   *          the JSON object to populate
097   * @param state
098   *          the schema generation state used for context and writing
099   * @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  interface IKey extends Comparable<IKey> {
135    Comparator<IKey> KEY_COMPARATOR = Comparator
136        .<IKey, String>comparing(key -> key.getDefinition().getContainingModule().getShortName())
137        .thenComparing(key -> key.getDefinition().getEffectiveName())
138        .thenComparing(IKey::getJsonKeyFlagName, Comparator.nullsFirst(Comparator.naturalOrder()))
139        .thenComparing(IKey::getDiscriminatorProperty, Comparator.nullsFirst(Comparator.naturalOrder()))
140        .thenComparing(IKey::getDiscriminatorValue, Comparator.nullsFirst(Comparator.naturalOrder()));
141
142    @NonNull
143    static IKey of(@NonNull IDefinition definition) {
144      return new SimpleKey(definition);
145    }
146
147    @NonNull
148    static IKey of(
149        @NonNull IDefinition definition,
150        @Nullable String jsonKeyFlagName,
151        @Nullable String discriminatorProperty,
152        @Nullable String discriminatorValue) {
153      IKey retval;
154      if (jsonKeyFlagName != null || discriminatorProperty != null || discriminatorValue != null) {
155        retval = new ComplexKey(definition, jsonKeyFlagName, discriminatorProperty, discriminatorValue);
156      } else {
157        retval = of(definition);
158      }
159      return retval;
160    }
161
162    @NonNull
163    IDefinition getDefinition();
164
165    @Nullable
166    String getJsonKeyFlagName();
167
168    @Nullable
169    String getDiscriminatorProperty();
170
171    @Nullable
172    String getDiscriminatorValue();
173
174    @Override
175    default int compareTo(IKey other) {
176      return KEY_COMPARATOR.compare(this, other);
177    }
178  }
179}