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