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" }) T schemaWriter = newWriter(out);
088      S generationState = newGenerationState(metaschema, schemaWriter, configuration);
089      generateSchema(generationState);
090      generationState.flushWriter();
091    } catch (SchemaGenerationException ex) { // NOPMD avoid nesting same exception
092      throw ex;
093    } catch (Exception ex) { // NOPMD need to catch close exception
094      throw new SchemaGenerationException(ex);
095    }
096  }
097
098  /**
099   * Determine the collection of root definitions.
100   *
101   * @param generationState
102   *          the schema generation state used for context and writing
103   * @param handler
104   *          a callback to execute on each identified root definition
105   * @return the list of identified root definitions
106   */
107  protected List<IAssemblyDefinition> analyzeDefinitions(
108      @NonNull S generationState,
109      @Nullable BiConsumer<ModuleIndex.DefinitionEntry, IDefinition> handler) {
110    // TODO: use of handler here is confusing and introduces side effects. Consider
111    // refactoring this in
112    // the caller
113
114    List<IAssemblyDefinition> rootAssemblyDefinitions = new LinkedList<>();
115    for (ModuleIndex.DefinitionEntry entry : generationState.getMetaschemaIndex().getDefinitions()) {
116
117      IDefinition definition = ObjectUtils.notNull(entry.getDefinition());
118      if (definition instanceof IAssemblyDefinition && ((IAssemblyDefinition) definition).isRoot()) {
119        // found root definition
120        IAssemblyDefinition assemblyDefinition = (IAssemblyDefinition) definition;
121        rootAssemblyDefinitions.add(assemblyDefinition);
122      }
123
124      boolean referenced = entry.isReferenced();
125      if (!referenced) {
126        // skip unreferenced definitions
127        continue;
128      }
129
130      if (handler != null) {
131        handler.accept(entry, definition);
132      }
133    }
134    return rootAssemblyDefinitions;
135  }
136
137}