From b76c449f6b5cbcd182ef2a56f31993438e1c065a Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Tue, 27 Feb 2024 06:28:35 -0500 Subject: [PATCH] Enable user-provided YAML configuration objects Signed-off-by: Michael Edgar --- .../yamljson/SnakeYamlEngineGenerator.java | 22 +++++-- .../io/xlate/yamljson/SnakeYamlGenerator.java | 26 ++++++-- src/main/java/io/xlate/yamljson/Yaml.java | 53 +++++++++++++-- .../java/io/xlate/yamljson/YamlGenerator.java | 39 ++++------- .../xlate/yamljson/YamlGeneratorFactory.java | 26 +++++--- .../io/xlate/yamljson/YamlParserFactory.java | 17 +++-- .../io/xlate/yamljson/YamlGeneratorTest.java | 65 +++++++++++++++++++ 7 files changed, 193 insertions(+), 55 deletions(-) diff --git a/src/main/java/io/xlate/yamljson/SnakeYamlEngineGenerator.java b/src/main/java/io/xlate/yamljson/SnakeYamlEngineGenerator.java index 4564a4b..7676f65 100644 --- a/src/main/java/io/xlate/yamljson/SnakeYamlEngineGenerator.java +++ b/src/main/java/io/xlate/yamljson/SnakeYamlEngineGenerator.java @@ -48,14 +48,14 @@ class SnakeYamlEngineGenerator extends YamlGenerator impleme static final Map EVENTS = new EnumMap<>(EventType.class); static final Map STYLES = new EnumMap<>(StyleType.class); + static final Event DOCUMENT_START_DEFAULT = new DocumentStartEvent(false, Optional.empty(), Collections.emptyMap()); + static final Event DOCUMENT_START_EXPLICIT = new DocumentStartEvent(true, Optional.empty(), Collections.emptyMap()); + static final Event DOCUMENT_END_DEFAULT = new DocumentEndEvent(false); + static final Event DOCUMENT_END_EXPLICIT = new DocumentEndEvent(true); static { EVENTS.put(EventType.STREAM_START, new StreamStartEvent()); EVENTS.put(EventType.STREAM_END, new StreamEndEvent()); - EVENTS.put(EventType.DOCUMENT_START_DEFAULT, new DocumentStartEvent(false, Optional.empty(), Collections.emptyMap())); - EVENTS.put(EventType.DOCUMENT_START_EXPLICIT, new DocumentStartEvent(true, Optional.empty(), Collections.emptyMap())); - EVENTS.put(EventType.DOCUMENT_END_DEFAULT, new DocumentEndEvent(false)); - EVENTS.put(EventType.DOCUMENT_END_EXPLICIT, new DocumentEndEvent(true)); EVENTS.put(EventType.MAPPING_START, new MappingStartEvent(Optional.empty(), Optional.empty(), true, FlowStyle.AUTO)); EVENTS.put(EventType.MAPPING_END, new MappingEndEvent()); EVENTS.put(EventType.SEQUENCE_START, new SequenceStartEvent(Optional.empty(), Optional.empty(), true, FlowStyle.AUTO)); @@ -68,7 +68,7 @@ class SnakeYamlEngineGenerator extends YamlGenerator impleme final Emitter emitter; SnakeYamlEngineGenerator(DumpSettings settings, YamlWriterStream writer) { - super(EVENTS, STYLES, writer, settings.isExplicitStart(), settings.isExplicitEnd()); + super(STYLES, writer); this.settings = settings; this.emitter = new Emitter(settings, writer); } @@ -77,6 +77,18 @@ class SnakeYamlEngineGenerator extends YamlGenerator impleme this(settings, new YamlWriterStream(writer)); } + @Override + protected Event getEvent(EventType type) { + switch (type) { + case DOCUMENT_START: + return settings.isExplicitStart() ? DOCUMENT_START_EXPLICIT : DOCUMENT_START_DEFAULT; + case DOCUMENT_END: + return settings.isExplicitEnd() ? DOCUMENT_END_EXPLICIT : DOCUMENT_END_DEFAULT; + default: + return EVENTS.get(type); + } + } + @Override protected void emitEvent(Event event) { emitter.emit(event); diff --git a/src/main/java/io/xlate/yamljson/SnakeYamlGenerator.java b/src/main/java/io/xlate/yamljson/SnakeYamlGenerator.java index 62bc92e..544e65e 100644 --- a/src/main/java/io/xlate/yamljson/SnakeYamlGenerator.java +++ b/src/main/java/io/xlate/yamljson/SnakeYamlGenerator.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.stream.Stream; +import jakarta.json.stream.JsonGenerator; + import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions.FlowStyle; import org.yaml.snakeyaml.DumperOptions.ScalarStyle; @@ -38,22 +40,20 @@ import org.yaml.snakeyaml.events.StreamEndEvent; import org.yaml.snakeyaml.events.StreamStartEvent; -import jakarta.json.stream.JsonGenerator; - class SnakeYamlGenerator extends YamlGenerator implements JsonGenerator { static final ImplicitTuple omitTags = new ImplicitTuple(true, true); static final Map EVENTS = new EnumMap<>(EventType.class); static final Map STYLES = new EnumMap<>(StyleType.class); + static final Event DOCUMENT_START_DEFAULT = new DocumentStartEvent(null, null, false, null, Collections.emptyMap()); + static final Event DOCUMENT_START_EXPLICIT = new DocumentStartEvent(null, null, true, null, Collections.emptyMap()); + static final Event DOCUMENT_END_DEFAULT = new DocumentEndEvent(null, null, false); + static final Event DOCUMENT_END_EXPLICIT = new DocumentEndEvent(null, null, true); static { EVENTS.put(EventType.STREAM_START, new StreamStartEvent(null, null)); EVENTS.put(EventType.STREAM_END, new StreamEndEvent(null, null)); - EVENTS.put(EventType.DOCUMENT_START_DEFAULT, new DocumentStartEvent(null, null, false, null, Collections.emptyMap())); - EVENTS.put(EventType.DOCUMENT_START_EXPLICIT, new DocumentStartEvent(null, null, true, null, Collections.emptyMap())); - EVENTS.put(EventType.DOCUMENT_END_DEFAULT, new DocumentEndEvent(null, null, false)); - EVENTS.put(EventType.DOCUMENT_END_EXPLICIT, new DocumentEndEvent(null, null, true)); EVENTS.put(EventType.MAPPING_START, new MappingStartEvent(null, null, true, null, null, FlowStyle.AUTO)); EVENTS.put(EventType.MAPPING_END, new MappingEndEvent(null, null)); EVENTS.put(EventType.SEQUENCE_START, new SequenceStartEvent(null, null, true, null, null, FlowStyle.AUTO)); @@ -66,11 +66,23 @@ class SnakeYamlGenerator extends YamlGenerator implements Js final Emitter emitter; SnakeYamlGenerator(DumperOptions settings, Writer writer) { - super(EVENTS, STYLES, writer, settings.isExplicitStart(), settings.isExplicitEnd()); + super(STYLES, writer); this.settings = settings; this.emitter = new Emitter(writer, settings); } + @Override + protected Event getEvent(EventType type) { + switch (type) { + case DOCUMENT_START: + return settings.isExplicitStart() ? DOCUMENT_START_EXPLICIT : DOCUMENT_START_DEFAULT; + case DOCUMENT_END: + return settings.isExplicitEnd() ? DOCUMENT_END_EXPLICIT : DOCUMENT_END_DEFAULT; + default: + return EVENTS.get(type); + } + } + @Override protected void emitEvent(Event event) throws IOException { emitter.emit(event); diff --git a/src/main/java/io/xlate/yamljson/Yaml.java b/src/main/java/io/xlate/yamljson/Yaml.java index 083a6ec..9574a57 100644 --- a/src/main/java/io/xlate/yamljson/Yaml.java +++ b/src/main/java/io/xlate/yamljson/Yaml.java @@ -110,7 +110,6 @@ private Settings() { * * @see Versions#V1_1 * @see Versions#V1_2 - * */ public static final String YAML_VERSION = "io.xlate.yamljson.YAML_VERSION"; @@ -128,17 +127,30 @@ private Settings() { * @see org.snakeyaml.engine.v2.api.LoadSettingsBuilder#setUseMarks(boolean) * * @since 0.1.0 + * @deprecated use + * {@link org.snakeyaml.engine.v2.api.LoadSettingsBuilder#setUseMarks(boolean) + * LoadSettingsBuilder#setUseMarks} with + * {@link #LOAD_CONFIG} to configure this option. */ - public static final String LOAD_USE_MARKS = "LOAD_USE_MARKS"; + @Deprecated(since = "0.2", forRemoval = true) + public static final String LOAD_USE_MARKS = "LOAD_USE_MARKS"; // NOSONAR /** - * Set to true if the document start must be explicitly indicated by adding - * {@code ---} at the beginning of the document. + * Set to true if the document start must be explicitly indicated by + * adding {@code ---} at the beginning of the document. * * @see org.yaml.snakeyaml.DumperOptions#setExplicitStart(boolean) * @see org.snakeyaml.engine.v2.api.DumpSettingsBuilder#setExplicitStart(boolean) + * + * @deprecated use + * {@link org.yaml.snakeyaml.DumperOptions#setExplicitStart(boolean) + * DumperOptions#setExplicitStart} or + * {@link org.snakeyaml.engine.v2.api.DumpSettingsBuilder#setExplicitStart(boolean) + * DumpSettingsBuilder#setExplicitStart} with + * {@link #DUMP_CONFIG} to configure this option. */ - public static final String DUMP_EXPLICIT_START = "DUMP_EXPLICIT_START"; + @Deprecated(since = "0.2", forRemoval = true) + public static final String DUMP_EXPLICIT_START = "DUMP_EXPLICIT_START"; // NOSONAR /** * Set to true if the document end must be explicitly indicated by @@ -146,8 +158,37 @@ private Settings() { * * @see org.yaml.snakeyaml.DumperOptions#setExplicitEnd(boolean) * @see org.snakeyaml.engine.v2.api.DumpSettingsBuilder#setExplicitEnd(boolean) + * + * @deprecated use + * {@link org.yaml.snakeyaml.DumperOptions#setExplicitEnd(boolean) + * DumperOptions#setExplicitEnd} or + * {@link org.snakeyaml.engine.v2.api.DumpSettingsBuilder#setExplicitEnd(boolean) + * DumpSettingsBuilder#setExplicitEnd} with + * {@link #DUMP_CONFIG} to configure this option. + */ + @Deprecated(since = "0.2", forRemoval = true) + public static final String DUMP_EXPLICIT_END = "DUMP_EXPLICIT_END"; // NOSONAR + + /** + * Used to pass a pre-configured + * {@linkplain org.yaml.snakeyaml.LoaderOptions LoaderOptions} or + * {@linkplain org.snakeyaml.engine.v2.api.LoadSettings LoadSettings} + * instance for use when reading or parsing YAML. + * + * @since 0.2 + */ + public static final String LOAD_CONFIG = "io.xlate.yamljson.LOAD_CONFIG"; + + /** + * Used to pass a pre-configured + * {@linkplain org.yaml.snakeyaml.DumperOptions DumperOptions} or + * {@linkplain org.snakeyaml.engine.v2.api.DumpSettings DumpSettings} + * instance for use when writing or generating YAML. + * + * @since 0.2 */ - public static final String DUMP_EXPLICIT_END = "DUMP_EXPLICIT_END"; + public static final String DUMP_CONFIG = "io.xlate.yamljson.DUMP_CONFIG"; + } private static final YamlProvider PROVIDER = new YamlProvider(); diff --git a/src/main/java/io/xlate/yamljson/YamlGenerator.java b/src/main/java/io/xlate/yamljson/YamlGenerator.java index e251566..0c29ef7 100644 --- a/src/main/java/io/xlate/yamljson/YamlGenerator.java +++ b/src/main/java/io/xlate/yamljson/YamlGenerator.java @@ -43,10 +43,8 @@ enum ContextType { enum EventType { STREAM_START, STREAM_END, - DOCUMENT_START_DEFAULT, - DOCUMENT_START_EXPLICIT, - DOCUMENT_END_DEFAULT, - DOCUMENT_END_EXPLICIT, + DOCUMENT_START, + DOCUMENT_END, MAPPING_START, MAPPING_END, SEQUENCE_START, @@ -73,30 +71,23 @@ interface IOOperation { static final StringQuotingChecker quoteChecker = new StringQuotingChecker(); - protected final Map eventTypes; protected final Map styleTypes; protected final Writer writer; - final boolean explicitStart; - final boolean explicitEnd; final Deque context = new ArrayDeque<>(); - YamlGenerator(Map eventTypes, Map styleTypes, Writer writer, boolean explicitStart, boolean explicitEnd) { - this.eventTypes = eventTypes; + YamlGenerator(Map styleTypes, Writer writer) { this.styleTypes = styleTypes; this.writer = writer; - this.explicitStart = explicitStart; - this.explicitEnd = explicitEnd; } + protected abstract E getEvent(EventType type); protected abstract void emitEvent(E event) throws IOException; protected abstract E buildScalarEvent(String scalarValue, S style); void ensureDocumentStarted() { if (context.isEmpty()) { - emit(eventTypes.get(EventType.STREAM_START)); - emit(explicitStart ? - eventTypes.get(EventType.DOCUMENT_START_EXPLICIT) : - eventTypes.get(EventType.DOCUMENT_START_DEFAULT)); + emit(getEvent(EventType.STREAM_START)); + emit(getEvent(EventType.DOCUMENT_START)); } } @@ -168,7 +159,7 @@ protected static void execute(String name, IOOperation operation) { public JsonGenerator writeStartObject() { ensureDocumentStarted(); context.push(ContextType.OBJECT); - emit(eventTypes.get(EventType.MAPPING_START)); + emit(getEvent(EventType.MAPPING_START)); return this; } @@ -177,7 +168,7 @@ public JsonGenerator writeStartObject(String name) { Objects.requireNonNull(name, "name"); writeKey(name); context.push(ContextType.OBJECT); - emit(eventTypes.get(EventType.MAPPING_START)); + emit(getEvent(EventType.MAPPING_START)); return this; } @@ -193,7 +184,7 @@ public JsonGenerator writeKey(String name) { public JsonGenerator writeStartArray() { ensureDocumentStarted(); context.push(ContextType.ARRAY); - emit(eventTypes.get(EventType.SEQUENCE_START)); + emit(getEvent(EventType.SEQUENCE_START)); return this; } @@ -202,7 +193,7 @@ public JsonGenerator writeStartArray(String name) { Objects.requireNonNull(name, "name"); writeKey(name); context.push(ContextType.ARRAY); - emit(eventTypes.get(EventType.SEQUENCE_START)); + emit(getEvent(EventType.SEQUENCE_START)); return this; } @@ -269,16 +260,14 @@ public JsonGenerator writeEnd() { ContextType contextType = this.context.pop(); if (contextType == ContextType.OBJECT) { - emit(eventTypes.get(EventType.MAPPING_END)); + emit(getEvent(EventType.MAPPING_END)); } else { - emit(eventTypes.get(EventType.SEQUENCE_END)); + emit(getEvent(EventType.SEQUENCE_END)); } if (this.context.isEmpty()) { - emit(explicitEnd ? - eventTypes.get(EventType.DOCUMENT_END_EXPLICIT) : - eventTypes.get(EventType.DOCUMENT_END_DEFAULT)); - emit(eventTypes.get(EventType.STREAM_END)); + emit(getEvent(EventType.DOCUMENT_END)); + emit(getEvent(EventType.STREAM_END)); } return this; diff --git a/src/main/java/io/xlate/yamljson/YamlGeneratorFactory.java b/src/main/java/io/xlate/yamljson/YamlGeneratorFactory.java index cded889..f441ac6 100644 --- a/src/main/java/io/xlate/yamljson/YamlGeneratorFactory.java +++ b/src/main/java/io/xlate/yamljson/YamlGeneratorFactory.java @@ -52,18 +52,28 @@ static void copyBoolean(Map properties, String name, Consumer properties) { - DumperOptions options = new DumperOptions(); - copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_START, options::setExplicitStart); - copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_END, options::setExplicitEnd); - return options; + return Optional.ofNullable(properties.get(Yaml.Settings.DUMP_CONFIG)) + .map(DumperOptions.class::cast) + .orElseGet(() -> { + DumperOptions options = new DumperOptions(); + copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_START, options::setExplicitStart); + copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_END, options::setExplicitEnd); + return options; + }); } + @SuppressWarnings("removal") static DumpSettings buildDumpSettings(Map properties) { - DumpSettingsBuilder settings = DumpSettings.builder(); - copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_START, settings::setExplicitStart); - copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_END, settings::setExplicitEnd); - return settings.build(); + return Optional.ofNullable(properties.get(Yaml.Settings.DUMP_CONFIG)) + .map(DumpSettings.class::cast) + .orElseGet(() -> { + DumpSettingsBuilder settings = DumpSettings.builder(); + copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_START, settings::setExplicitStart); + copyBoolean(properties, Yaml.Settings.DUMP_EXPLICIT_END, settings::setExplicitEnd); + return settings.build(); + }); } private final Map properties; diff --git a/src/main/java/io/xlate/yamljson/YamlParserFactory.java b/src/main/java/io/xlate/yamljson/YamlParserFactory.java index ab60eb0..e94c609 100644 --- a/src/main/java/io/xlate/yamljson/YamlParserFactory.java +++ b/src/main/java/io/xlate/yamljson/YamlParserFactory.java @@ -44,15 +44,24 @@ class YamlParserFactory implements JsonParserFactory, SettingsBuilder { static final Function, Object> SNAKEYAML_FACTORY = // No load properties supported currently - props -> new org.yaml.snakeyaml.Yaml(new LoaderOptions()); + props -> new org.yaml.snakeyaml.Yaml(buildLoaderOptions(props)); static final Function, Object> SNAKEYAML_ENGINE_FACTORY = props -> new org.snakeyaml.engine.v2.api.lowlevel.Parse(buildLoadSettings(props)); + static LoaderOptions buildLoaderOptions(Map properties) { + return Optional.ofNullable(properties.get(Yaml.Settings.LOAD_CONFIG)) + .map(LoaderOptions.class::cast) + .orElseGet(LoaderOptions::new); + } + + @SuppressWarnings("removal") static LoadSettings buildLoadSettings(Map properties) { - return LoadSettings.builder() - .setUseMarks(getProperty(properties, Yaml.Settings.LOAD_USE_MARKS, Boolean::valueOf, true)) - .build(); + return Optional.ofNullable(properties.get(Yaml.Settings.LOAD_CONFIG)) + .map(LoadSettings.class::cast) + .orElseGet(() -> LoadSettings.builder() + .setUseMarks(getProperty(properties, Yaml.Settings.LOAD_USE_MARKS, Boolean::valueOf, true)) + .build()); } private final Map properties; diff --git a/src/test/java/io/xlate/yamljson/YamlGeneratorTest.java b/src/test/java/io/xlate/yamljson/YamlGeneratorTest.java index dae65cf..26c72c5 100644 --- a/src/test/java/io/xlate/yamljson/YamlGeneratorTest.java +++ b/src/test/java/io/xlate/yamljson/YamlGeneratorTest.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.snakeyaml.engine.v2.api.DumpSettings; +import org.yaml.snakeyaml.DumperOptions; import jakarta.json.stream.JsonGenerationException; import jakarta.json.stream.JsonGenerator; @@ -171,6 +173,37 @@ void testMappingOfValues(String version) { @ParameterizedTest @MethodSource(VERSIONS_SOURCE) void testExplicitDocumentStart(String version) { + Object config; + + if (Yaml.Versions.V1_1.endsWith(version)) { + DumperOptions opt = new DumperOptions(); + opt.setExplicitStart(true); + config = opt; + } else { + config = DumpSettings.builder() + .setExplicitStart(true) + .build(); + } + + JsonGeneratorFactory factory = Yaml.createGeneratorFactory(Map.of(Yaml.Settings.DUMP_CONFIG, config, + Yaml.Settings.YAML_VERSION, version)); + StringWriter writer = new StringWriter(); + + try (JsonGenerator generator = factory.createGenerator(writer)) { + generator.writeStartObject() + .write("testKey", "testValue") + .writeEnd(); + + writer.flush(); + } + + assertEquals("---\ntestKey: testValue\n", writer.toString()); + } + + @Deprecated + @ParameterizedTest + @MethodSource(VERSIONS_SOURCE) + void testExplicitDocumentStartWithDeprecatedProperty(String version) { JsonGeneratorFactory factory = Yaml.createGeneratorFactory(Map.of(Yaml.Settings.DUMP_EXPLICIT_START, "true", Yaml.Settings.YAML_VERSION, version)); StringWriter writer = new StringWriter(); @@ -189,6 +222,38 @@ void testExplicitDocumentStart(String version) { @ParameterizedTest @MethodSource(VERSIONS_SOURCE) void testExplicitDocumentEnd(String version) { + Object config; + + if (Yaml.Versions.V1_1.endsWith(version)) { + DumperOptions opt = new DumperOptions(); + opt.setExplicitEnd(true); + config = opt; + } else { + config = DumpSettings.builder() + .setExplicitEnd(true) + .build(); + } + + JsonGeneratorFactory factory = Yaml.createGeneratorFactory(Map.of(Yaml.Settings.DUMP_CONFIG, config, + Yaml.Settings.YAML_VERSION, version)); + + StringWriter writer = new StringWriter(); + + try (JsonGenerator generator = factory.createGenerator(writer)) { + generator.writeStartObject() + .write("testKey", "testValue") + .writeEnd(); + + writer.flush(); + } + + assertEquals("testKey: testValue\n...\n", writer.toString()); + } + + @Deprecated + @ParameterizedTest + @MethodSource(VERSIONS_SOURCE) + void testExplicitDocumentEndWithDeprecatedProperty(String version) { JsonGeneratorFactory factory = Yaml.createGeneratorFactory(Map.of(Yaml.Settings.DUMP_EXPLICIT_END, "true", Yaml.Settings.YAML_VERSION, version)); StringWriter writer = new StringWriter();