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}