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}