001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.schemagen;
007
008import org.eclipse.jdt.annotation.Owning;
009
010import java.io.Writer;
011import java.util.LinkedList;
012import java.util.List;
013import java.util.function.BiConsumer;
014
015import dev.metaschema.core.configuration.IConfiguration;
016import dev.metaschema.core.model.IAssemblyDefinition;
017import dev.metaschema.core.model.IDefinition;
018import dev.metaschema.core.model.IModule;
019import dev.metaschema.core.util.ObjectUtils;
020import dev.metaschema.schemagen.datatype.IDatatypeManager;
021import edu.umd.cs.findbugs.annotations.NonNull;
022import edu.umd.cs.findbugs.annotations.Nullable;
023
024/**
025 * Thsi abstract class provides a common implementation shared by all schema
026 * generators.
027 *
028 * @param <T>
029 *          the writer type
030 * @param <D>
031 *          the {@link IDatatypeManager} type
032 * @param <S>
033 *          the {@link IGenerationState} type
034 */
035public abstract class AbstractSchemaGenerator<
036    T extends AutoCloseable,
037    D extends IDatatypeManager,
038    S extends AbstractGenerationState<
039        T, D>>
040    implements ISchemaGenerator {
041
042  /**
043   * Create a new writer to use to write the schema.
044   * <p>
045   * The caller owns the returned writer and is responsible for closing it.
046   *
047   * @param out
048   *          the {@link Writer} to write the schema content to
049   * @return the schema writer
050   * @throws SchemaGenerationException
051   *           if an error occurred while creating the writer
052   */
053  @NonNull
054  @Owning
055  protected abstract T newWriter(@NonNull Writer out);
056
057  /**
058   * Create a new schema generation state object.
059   *
060   * @param module
061   *          the Metaschema module to generate the schema for
062   * @param schemaWriter
063   *          the writer to use to write the schema
064   * @param configuration
065   *          the generation configuration
066   * @return the schema generation state used for context and writing
067   * @throws SchemaGenerationException
068   *           if an error occurred while creating the generation state object
069   */
070  @NonNull
071  protected abstract S newGenerationState(
072      @NonNull IModule module,
073      @NonNull T schemaWriter,
074      @NonNull IConfiguration<SchemaGenerationFeature<?>> configuration);
075
076  /**
077   * Called to generate the actual schema content.
078   *
079   * @param generationState
080   *          the generation state object
081   */
082  protected abstract void generateSchema(@NonNull S generationState);
083
084  @Override
085  public void generateFromModule(
086      IModule metaschema,
087      Writer out,
088      IConfiguration<SchemaGenerationFeature<?>> configuration) {
089    try {
090      // avoid automatically closing streams not owned by the generator
091      @SuppressWarnings("resource")
092      T schemaWriter = newWriter(out);
093      S generationState = newGenerationState(metaschema, schemaWriter, configuration);
094      generateSchema(generationState);
095      generationState.flushWriter();
096    } catch (SchemaGenerationException ex) {
097      throw ex;
098    } catch (Exception ex) { // NOPMD need to catch close exception
099      throw new SchemaGenerationException(ex);
100    }
101  }
102
103  /**
104   * Determine the collection of root definitions.
105   *
106   * @param generationState
107   *          the schema generation state used for context and writing
108   * @param handler
109   *          a callback to execute on each identified root definition
110   * @return the list of identified root definitions
111   */
112  protected List<IAssemblyDefinition> analyzeDefinitions(
113      @NonNull S generationState,
114      @Nullable BiConsumer<ModuleIndex.DefinitionEntry, IDefinition> handler) {
115    // TODO: use of handler here is confusing and introduces side effects. Consider
116    // refactoring this in the caller
117    List<IAssemblyDefinition> rootAssemblyDefinitions = new LinkedList<>();
118    for (ModuleIndex.DefinitionEntry entry : generationState.getMetaschemaIndex().getDefinitions()) {
119
120      IDefinition definition = ObjectUtils.notNull(entry.getDefinition());
121      if (definition instanceof IAssemblyDefinition && ((IAssemblyDefinition) definition).isRoot()) {
122        // found root definition
123        IAssemblyDefinition assemblyDefinition = (IAssemblyDefinition) definition;
124        rootAssemblyDefinitions.add(assemblyDefinition);
125      }
126
127      boolean referenced = entry.isReferenced();
128      if (!referenced) {
129        // skip unreferenced definitions
130        continue;
131      }
132
133      if (handler != null) {
134        handler.accept(entry, definition);
135      }
136    }
137    return rootAssemblyDefinitions;
138  }
139
140}