001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.databind.io.json;
007
008import com.fasterxml.jackson.core.JsonFactory;
009import com.fasterxml.jackson.core.JsonGenerator;
010import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
011
012import java.io.IOException;
013import java.io.Writer;
014
015import dev.metaschema.core.configuration.IMutableConfiguration;
016import dev.metaschema.core.model.IBoundObject;
017import dev.metaschema.core.util.ObjectUtils;
018import dev.metaschema.databind.io.AbstractSerializer;
019import dev.metaschema.databind.io.SerializationFeature;
020import dev.metaschema.databind.model.IBoundDefinitionModelAssembly;
021import edu.umd.cs.findbugs.annotations.NonNull;
022import nl.talsmasoftware.lazy4j.Lazy;
023
024/**
025 * Provides support for serializing bound Java objects to JSON format based on a
026 * Metaschema module definition.
027 * <p>
028 * This serializer uses Jackson's {@link JsonGenerator} to produce JSON output
029 * that conforms to the Metaschema-defined data structure.
030 *
031 * @param <CLASS>
032 *          the Java type of the bound object to be serialized
033 */
034public class DefaultJsonSerializer<CLASS extends IBoundObject>
035    extends AbstractSerializer<CLASS> {
036  private Lazy<JsonFactory> factory;
037
038  /**
039   * Construct a new Module binding-based deserializer that reads JSON-based
040   * Module content.
041   *
042   * @param definition
043   *          the assembly class binding describing the Java objects this
044   *          deserializer parses data into
045   */
046  public DefaultJsonSerializer(@NonNull IBoundDefinitionModelAssembly definition) {
047    super(definition);
048    resetFactory();
049  }
050
051  /**
052   * Resets the JSON factory to use a freshly created instance.
053   * <p>
054   * This method is called when the serializer configuration changes to ensure the
055   * factory reflects the current settings.
056   */
057  protected final void resetFactory() {
058    this.factory = Lazy.of(this::newFactoryInstance);
059  }
060
061  @Override
062  protected void configurationChanged(IMutableConfiguration<SerializationFeature<?>> config) {
063    super.configurationChanged(config);
064    resetFactory();
065  }
066
067  /**
068   * Constructs a new JSON factory.
069   * <p>
070   * Subclasses can override this method to create a JSON factory with a specific
071   * configuration.
072   *
073   * @return the factory
074   */
075  @NonNull
076  protected JsonFactory newFactoryInstance() {
077    return JsonFactoryFactory.instance();
078  }
079
080  /**
081   * Get the configured JSON factory instance.
082   *
083   * @return the JSON factory used to create JSON generators
084   */
085  @NonNull
086  private JsonFactory getJsonFactory() {
087    return ObjectUtils.notNull(factory.get());
088  }
089
090  /**
091   * Create a new JSON generator for writing to the provided writer.
092   *
093   * @param writer
094   *          the writer to send JSON output to
095   * @return a new JSON generator configured with pretty printing
096   * @throws IOException
097   *           if an error occurs while creating the generator
098   */
099  @SuppressWarnings("resource")
100  @NonNull
101  private JsonGenerator newJsonGenerator(@NonNull Writer writer) throws IOException {
102    JsonFactory factory = getJsonFactory();
103    return ObjectUtils.notNull(factory.createGenerator(writer)
104        .setPrettyPrinter(new DefaultPrettyPrinter()));
105  }
106
107  @Override
108  public void serialize(IBoundObject data, Writer writer) throws IOException {
109    try (JsonGenerator generator = newJsonGenerator(writer)) {
110      IBoundDefinitionModelAssembly definition = getDefinition();
111
112      boolean serializeRoot = get(SerializationFeature.SERIALIZE_ROOT);
113      if (serializeRoot) {
114        // first write the initial START_OBJECT
115        generator.writeStartObject();
116
117        generator.writeFieldName(definition.getRootJsonName());
118      }
119
120      MetaschemaJsonWriter jsonWriter = new MetaschemaJsonWriter(generator);
121      jsonWriter.write(definition, data);
122
123      if (serializeRoot) {
124        generator.writeEndObject();
125      }
126    }
127  }
128}