From 3b277c5e10ebb12e5c35c2a554761d04728c9b21 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 4 Oct 2024 15:04:18 +0200 Subject: [PATCH 01/10] Added: YAMLParserExt adding extended yaml parsing with anchor support --- .../dataformat/yaml/YAMLParserExt.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java new file mode 100644 index 00000000..14ce416d --- /dev/null +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java @@ -0,0 +1,170 @@ +package com.fasterxml.jackson.dataformat.yaml; + +import java.io.Reader; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.events.AliasEvent; +import org.yaml.snakeyaml.events.Event; +import org.yaml.snakeyaml.events.MappingEndEvent; +import org.yaml.snakeyaml.events.MappingStartEvent; +import org.yaml.snakeyaml.events.NodeEvent; +import org.yaml.snakeyaml.events.ScalarEvent; +import org.yaml.snakeyaml.events.CollectionEndEvent; +import org.yaml.snakeyaml.events.CollectionStartEvent; +import org.yaml.snakeyaml.nodes.MappingNode; + +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.io.IOContext; + +/** + * A parser that remembers the events of anchored parts in yaml and repeats them + * to inline these parts when an alias if found instead of only returning an alias. + * + * Note: this overwrites the getEvent() since the base `super.nextToken()` manages to much state and + * it seems to be much simpler to re-emit the events. + */ +public class YAMLParserExt extends YAMLParser { + private static class AnchorContext { + public final String anchor; + public final List events = new ArrayList<>(); + public int depth = 1; + + public AnchorContext(String anchor) { + this.anchor = anchor; + } + } + + /** + * Remembers when a merge has been started in order to skip the corresponding + * sequence end which needs to be excluded + */ + private Stack mergeStack = new Stack<>(); + + /** + * Collects nested anchor definitions + */ + private Stack tokenStack = new Stack<>(); + + /** + * Keeps track of the last sequentially found definition of each anchor + */ + private Map> referencedObjects = new HashMap<>(); + + /** + * Keeps track of events that have been insert when processing alias + */ + private ArrayDeque refEvents = new ArrayDeque<>(); + + /** + * keeps track of the global depth of nested collections + */ + private int global_depth = 0; + + public YAMLParserExt(IOContext ctxt, int parserFeatures, int formatFeatures, LoaderOptions loaderOptions, ObjectCodec codec, Reader reader) { + super(ctxt, parserFeatures, formatFeatures, loaderOptions, codec, reader); + } + + private void finishContext(AnchorContext context) { + referencedObjects.put(context.anchor, context.events); + if (!tokenStack.isEmpty()) { + tokenStack.peek().events.addAll(context.events); + } + } + + protected Event trackDepth(Event event) { + if (event instanceof CollectionStartEvent) { + ++global_depth; + } else if (event instanceof CollectionEndEvent) { + --global_depth; + } + return event; + } + + protected Event filterEvent(Event event) { + if (event instanceof MappingEndEvent) { + if (!mergeStack.isEmpty()) { + if (mergeStack.peek() > global_depth) { + mergeStack.pop(); + return null; + } + } + } + return event; + } + + @Override + protected Event getEvent() { + while(!refEvents.isEmpty()) {; + Event event = filterEvent(trackDepth(refEvents.removeFirst())); + if (event != null) return event; + } + + Event event = null; + while (event == null) { + event = trackDepth(super.getEvent()); + if (event == null) return null; + event = filterEvent(event); + } + + if (event instanceof AliasEvent) { + AliasEvent alias = (AliasEvent) event; + List events = referencedObjects.get(alias.getAnchor()); + if (events != null) { + refEvents.addAll(events); + return refEvents.removeFirst(); + } + throw new IllegalStateException("invalid alias " + alias.getAnchor()); + } + + if (event instanceof NodeEvent) { + String anchor = ((NodeEvent) event).getAnchor(); + if (anchor != null) { + AnchorContext context = new AnchorContext(anchor); + context.events.add(event); + if (event instanceof CollectionStartEvent) { + tokenStack.push(context); + } else { + // directly store it + finishContext(context); + } + return event; + } + } + + if (event instanceof ScalarEvent) { + ScalarEvent scalarEvent = (ScalarEvent) event; + if (scalarEvent.getValue().equals( "<<")) { + // expect next node to be a map + Event next = getEvent(); + if (next instanceof MappingStartEvent) { + mergeStack.push(global_depth); + return getEvent(); + } + throw new IllegalStateException("found field '<<' but value isn't a map"); + } + } + + if (!tokenStack.isEmpty()) { + AnchorContext context = tokenStack.peek(); + context.events.add(event); + if (event instanceof CollectionStartEvent) { + ++context.depth; + } else if (event instanceof CollectionEndEvent) { + --context.depth; + if (context.depth == 0) { + tokenStack.pop(); + finishContext(context); + } + } + } + return event; + } +} From a1140f309465c16d3cb3089507b7c0d0df17a956 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 4 Oct 2024 15:16:54 +0200 Subject: [PATCH 02/10] Added: introduced a factory to create YAMLParserExt --- .../dataformat/yaml/YAMLFactoryExt.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java new file mode 100644 index 00000000..1e3d079f --- /dev/null +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java @@ -0,0 +1,62 @@ +package com.fasterxml.jackson.dataformat.yaml; + +import java.io.CharArrayReader; +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.io.IOContext; + +/** + * A subclass of YAMLFactory with the only purpose to replace the YAMLParser by the YAMLParserExt subclass + */ +public class YAMLFactoryExt extends YAMLFactory { + public YAMLFactoryExt() { + super(); + } + + public YAMLFactoryExt(ObjectCodec oc) { + super(oc); + } + + public YAMLFactoryExt(YAMLFactory src, ObjectCodec oc) { + super(src, oc); + } + + protected YAMLFactoryExt(YAMLFactoryBuilder b) { + super(b); + } + + @Override + public YAMLFactoryExt copy() { + this._checkInvalidCopy(YAMLFactoryExt.class); + return new YAMLFactoryExt(this, (ObjectCodec) null); + } + + @Override + protected Object readResolve() { + return new YAMLFactoryExt(this, this._objectCodec); + } + + @Override + protected YAMLParser _createParser(InputStream input, IOContext ctxt) throws IOException { + return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(input, (JsonEncoding) null, ctxt)); + } + + @Override + protected YAMLParser _createParser(Reader r, IOContext ctxt) throws IOException { + return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, r); + } + + @Override + protected YAMLParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable) throws IOException { + return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, new CharArrayReader(data, offset, len)); + } + + @Override + protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException { + return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(data, offset, len, (JsonEncoding) null, ctxt)); + } +} From e94ea2a9056562d4e840f5763fbc2b99abede208 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 6 Dec 2024 11:01:19 +0100 Subject: [PATCH 03/10] Fixed: resolved review findings using ArrayDeque insteadof Stack Cleanup: renamed `global_depth` to `globalDepth` Cleanup: removed accidential added `;` --- .../jackson/dataformat/yaml/YAMLParserExt.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java index 14ce416d..6e21103b 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java @@ -46,12 +46,12 @@ public AnchorContext(String anchor) { * Remembers when a merge has been started in order to skip the corresponding * sequence end which needs to be excluded */ - private Stack mergeStack = new Stack<>(); + private ArrayDeque mergeStack = new ArrayDeque<>(); /** * Collects nested anchor definitions */ - private Stack tokenStack = new Stack<>(); + private ArrayDeque tokenStack = new ArrayDeque<>(); /** * Keeps track of the last sequentially found definition of each anchor @@ -66,7 +66,7 @@ public AnchorContext(String anchor) { /** * keeps track of the global depth of nested collections */ - private int global_depth = 0; + private int globalDepth = 0; public YAMLParserExt(IOContext ctxt, int parserFeatures, int formatFeatures, LoaderOptions loaderOptions, ObjectCodec codec, Reader reader) { super(ctxt, parserFeatures, formatFeatures, loaderOptions, codec, reader); @@ -81,9 +81,9 @@ private void finishContext(AnchorContext context) { protected Event trackDepth(Event event) { if (event instanceof CollectionStartEvent) { - ++global_depth; + ++globalDepth; } else if (event instanceof CollectionEndEvent) { - --global_depth; + --globalDepth; } return event; } @@ -91,7 +91,7 @@ protected Event trackDepth(Event event) { protected Event filterEvent(Event event) { if (event instanceof MappingEndEvent) { if (!mergeStack.isEmpty()) { - if (mergeStack.peek() > global_depth) { + if (mergeStack.peek() > globalDepth) { mergeStack.pop(); return null; } @@ -102,7 +102,7 @@ protected Event filterEvent(Event event) { @Override protected Event getEvent() { - while(!refEvents.isEmpty()) {; + while(!refEvents.isEmpty()) { Event event = filterEvent(trackDepth(refEvents.removeFirst())); if (event != null) return event; } @@ -145,7 +145,7 @@ protected Event getEvent() { // expect next node to be a map Event next = getEvent(); if (next instanceof MappingStartEvent) { - mergeStack.push(global_depth); + mergeStack.push(globalDepth); return getEvent(); } throw new IllegalStateException("found field '<<' but value isn't a map"); From 773e3a419be2e00cd049861d965ef1417c090b48 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 6 Dec 2024 11:03:38 +0100 Subject: [PATCH 04/10] Fixed: added missing `final` on some members --- .../fasterxml/jackson/dataformat/yaml/YAMLParserExt.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java index 6e21103b..b9a83108 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java @@ -46,22 +46,22 @@ public AnchorContext(String anchor) { * Remembers when a merge has been started in order to skip the corresponding * sequence end which needs to be excluded */ - private ArrayDeque mergeStack = new ArrayDeque<>(); + private final ArrayDeque mergeStack = new ArrayDeque<>(); /** * Collects nested anchor definitions */ - private ArrayDeque tokenStack = new ArrayDeque<>(); + private final ArrayDeque tokenStack = new ArrayDeque<>(); /** * Keeps track of the last sequentially found definition of each anchor */ - private Map> referencedObjects = new HashMap<>(); + private final Map> referencedObjects = new HashMap<>(); /** * Keeps track of events that have been insert when processing alias */ - private ArrayDeque refEvents = new ArrayDeque<>(); + private final ArrayDeque refEvents = new ArrayDeque<>(); /** * keeps track of the global depth of nested collections From 7dd85664a95927abf35356cbd5c38e61ee8ba511 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 6 Dec 2024 11:20:59 +0100 Subject: [PATCH 05/10] Renamed: java-file `YAML*Ext.java` to new class names `YAMLAnchorReplaying*.java` --- .../yaml/{YAMLFactoryExt.java => YAMLAnchorReplayingFactory.java} | 0 .../yaml/{YAMLParserExt.java => YAMLAnchorReplayingParser.java} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/{YAMLFactoryExt.java => YAMLAnchorReplayingFactory.java} (100%) rename yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/{YAMLParserExt.java => YAMLAnchorReplayingParser.java} (100%) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java similarity index 100% rename from yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java rename to yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java similarity index 100% rename from yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java rename to yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java From b547243e9f5dd0e4c12da93169766fbceaf5cf75 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 6 Dec 2024 11:23:33 +0100 Subject: [PATCH 06/10] Refactored: renamed classes `YAML*Ext.java` to `YAMLAnchorReplaying*.java` --- .../yaml/YAMLAnchorReplayingFactory.java | 28 +++++++++---------- .../yaml/YAMLAnchorReplayingParser.java | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java index 1e3d079f..01dbf784 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java @@ -10,53 +10,53 @@ import com.fasterxml.jackson.core.io.IOContext; /** - * A subclass of YAMLFactory with the only purpose to replace the YAMLParser by the YAMLParserExt subclass + * A subclass of YAMLFactory with the only purpose to replace the YAMLParser by the YAMLAnchorReplayingParser subclass */ -public class YAMLFactoryExt extends YAMLFactory { - public YAMLFactoryExt() { +public class YAMLAnchorReplayingFactory extends YAMLFactory { + public YAMLAnchorReplayingFactory() { super(); } - public YAMLFactoryExt(ObjectCodec oc) { + public YAMLAnchorReplayingFactory(ObjectCodec oc) { super(oc); } - public YAMLFactoryExt(YAMLFactory src, ObjectCodec oc) { + public YAMLAnchorReplayingFactory(YAMLFactory src, ObjectCodec oc) { super(src, oc); } - protected YAMLFactoryExt(YAMLFactoryBuilder b) { + protected YAMLAnchorReplayingFactory(YAMLFactoryBuilder b) { super(b); } @Override - public YAMLFactoryExt copy() { - this._checkInvalidCopy(YAMLFactoryExt.class); - return new YAMLFactoryExt(this, (ObjectCodec) null); + public YAMLAnchorReplayingFactory copy() { + this._checkInvalidCopy(YAMLAnchorReplayingFactory.class); + return new YAMLAnchorReplayingFactory(this, (ObjectCodec) null); } @Override protected Object readResolve() { - return new YAMLFactoryExt(this, this._objectCodec); + return new YAMLAnchorReplayingFactory(this, this._objectCodec); } @Override protected YAMLParser _createParser(InputStream input, IOContext ctxt) throws IOException { - return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(input, (JsonEncoding) null, ctxt)); + return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(input, (JsonEncoding) null, ctxt)); } @Override protected YAMLParser _createParser(Reader r, IOContext ctxt) throws IOException { - return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, r); + return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, r); } @Override protected YAMLParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable) throws IOException { - return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, new CharArrayReader(data, offset, len)); + return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, new CharArrayReader(data, offset, len)); } @Override protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException { - return new YAMLParserExt(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(data, offset, len, (JsonEncoding) null, ctxt)); + return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(data, offset, len, (JsonEncoding) null, ctxt)); } } diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java index b9a83108..615c8512 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java @@ -31,7 +31,7 @@ * Note: this overwrites the getEvent() since the base `super.nextToken()` manages to much state and * it seems to be much simpler to re-emit the events. */ -public class YAMLParserExt extends YAMLParser { +public class YAMLAnchorReplayingParser extends YAMLParser { private static class AnchorContext { public final String anchor; public final List events = new ArrayList<>(); @@ -68,7 +68,7 @@ public AnchorContext(String anchor) { */ private int globalDepth = 0; - public YAMLParserExt(IOContext ctxt, int parserFeatures, int formatFeatures, LoaderOptions loaderOptions, ObjectCodec codec, Reader reader) { + public YAMLAnchorReplayingParser(IOContext ctxt, int parserFeatures, int formatFeatures, LoaderOptions loaderOptions, ObjectCodec codec, Reader reader) { super(ctxt, parserFeatures, formatFeatures, loaderOptions, codec, reader); } From ffdafedf2f965ead5a4793ba168b0e1c2129705f Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Fri, 6 Dec 2024 12:57:28 +0100 Subject: [PATCH 07/10] Added: introduced constants for defining the upper limit --- .../yaml/YAMLAnchorReplayingParser.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java index 615c8512..a272380d 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java @@ -42,6 +42,26 @@ public AnchorContext(String anchor) { } } + /** + * the maximum number of events that can be replayed + */ + public static final int MAX_EVENTS = 9999; + + /** + * the maximum limit of anchors to remember + */ + public static final int MAX_ANCHORS = 9999; + + /** + * the maximum limit of merges to follow + */ + public static final int MAX_MERGES = 9999; + + /** + * the maximum limit of references to remember + */ + public static final int MAX_REFS = 9999; + /** * Remembers when a merge has been started in order to skip the corresponding * sequence end which needs to be excluded @@ -73,9 +93,12 @@ public YAMLAnchorReplayingParser(IOContext ctxt, int parserFeatures, int formatF } private void finishContext(AnchorContext context) { + if (referencedObjects.size() + 1 > MAX_REFS) throw new IllegalStateException("too many references in the document"); referencedObjects.put(context.anchor, context.events); if (!tokenStack.isEmpty()) { - tokenStack.peek().events.addAll(context.events); + List events = tokenStack.peek().events; + if (events.size() + context.events.size() > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); + events.addAll(context.events); } } @@ -118,6 +141,7 @@ protected Event getEvent() { AliasEvent alias = (AliasEvent) event; List events = referencedObjects.get(alias.getAnchor()); if (events != null) { + if (refEvents.size() + events.size() > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); refEvents.addAll(events); return refEvents.removeFirst(); } @@ -130,6 +154,7 @@ protected Event getEvent() { AnchorContext context = new AnchorContext(anchor); context.events.add(event); if (event instanceof CollectionStartEvent) { + if (tokenStack.size() + 1 > MAX_ANCHORS) throw new IllegalStateException("too many anchors in the document"); tokenStack.push(context); } else { // directly store it @@ -145,6 +170,7 @@ protected Event getEvent() { // expect next node to be a map Event next = getEvent(); if (next instanceof MappingStartEvent) { + if (mergeStack.size() + 1 > MAX_MERGES) throw new IllegalStateException("too many merges in the document"); mergeStack.push(globalDepth); return getEvent(); } @@ -154,6 +180,7 @@ protected Event getEvent() { if (!tokenStack.isEmpty()) { AnchorContext context = tokenStack.peek(); + if (context.events.size() + 1 > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); context.events.add(event); if (event instanceof CollectionStartEvent) { ++context.depth; From 19f6a1e371a543bb880bd21b092b4a59a139e336 Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Mon, 9 Dec 2024 12:15:29 +0100 Subject: [PATCH 08/10] Added: implemented a test for the YAMLAnchroReplayingParser --- ...StreamingYAMLAnchorReplayingParseTest.java | 375 ++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/StreamingYAMLAnchorReplayingParseTest.java diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/StreamingYAMLAnchorReplayingParseTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/StreamingYAMLAnchorReplayingParseTest.java new file mode 100644 index 00000000..6cc53656 --- /dev/null +++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/StreamingYAMLAnchorReplayingParseTest.java @@ -0,0 +1,375 @@ +package com.fasterxml.jackson.dataformat.yaml.deser; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import com.fasterxml.jackson.dataformat.yaml.JacksonYAMLParseException; +import com.fasterxml.jackson.dataformat.yaml.ModuleTestBase; +import com.fasterxml.jackson.dataformat.yaml.YAMLAnchorReplayingFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLAnchorReplayingParser; +import org.yaml.snakeyaml.LoaderOptions; + +public class StreamingYAMLAnchorReplayingParseTest extends ModuleTestBase { + + private final YAMLAnchorReplayingFactory YAML_F = new YAMLAnchorReplayingFactory(); + + public void testBasic() throws Exception + { + final String YAML = +"string: 'text'\n" ++"bool: true\n" ++"bool2: false\n" ++"null: null\n" ++"i: 123\n" ++"d: 1.25\n" +; + JsonParser p = YAML_F.createParser(YAML); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("text", p.getText()); + JsonLocation loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(9, loc.getColumnNr()); + assertEquals(8, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + assertEquals("true", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(21, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_FALSE, p.nextToken()); + assertEquals("false", p.getText()); + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_NULL, p.nextToken()); + assertEquals("null", p.getText()); + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("123", p.getText()); + assertEquals(123, p.getIntValue()); + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals("1.25", p.getText()); + assertEquals(1.25, p.getDoubleValue()); + assertEquals(1, p.getIntValue()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + assertNull(p.nextToken()); + assertNull(p.nextToken()); + assertNull(p.nextToken()); + p.close(); + } + + public void testScalarAnchor() throws Exception + { + final String YAML = +"string1: &stringAnchor 'textValue'\n" ++"string2: *stringAnchor\n" ++"int1: &intAnchor 123\n" ++"int2: *intAnchor\n" +; + + JsonParser p = YAML_F.createParser(YAML); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string1", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("textValue", p.getText()); + JsonLocation loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(10, loc.getColumnNr()); + assertEquals(9, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string2", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("textValue", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(10, loc.getColumnNr()); + assertEquals(9, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("int1", p.getText()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("123", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(64, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("int2", p.getText()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("123", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(64, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + assertNull(p.nextToken()); + assertNull(p.nextToken()); + assertNull(p.nextToken()); + p.close(); + } + + public void testSequenceAnchor() throws Exception + { + final String YAML = +"list1: &listAnchor\n" ++" - 1\n" ++" - 2\n" ++" - 3\n" ++"list2: *listAnchor\n" +; + JsonParser p = YAML_F.createParser(YAML); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("list1", p.getText()); + + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("1", p.getText()); + JsonLocation loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(23, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("2", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(29, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("3", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(4, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(35, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_ARRAY, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("list2", p.getText()); + + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("1", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(23, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("2", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(29, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals("3", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(4, loc.getLineNr()); + assertEquals(5, loc.getColumnNr()); + assertEquals(35, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_ARRAY, p.nextToken()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertNull(p.nextToken()); + + p.close(); + } + + public void testObjectAnchor() throws Exception + { + final String YAML = +"obj1: &objAnchor\n" ++" string: 'text'\n" ++" bool: True\n" ++"obj2: *objAnchor\n" +; + JsonParser p = YAML_F.createParser(YAML); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("obj1", p.getText()); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + JsonLocation loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(6, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("text", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(11, loc.getColumnNr()); + assertEquals(27, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("bool", p.getText()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(9, loc.getColumnNr()); + assertEquals(42, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("obj2", p.getText()); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(6, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("text", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(11, loc.getColumnNr()); + assertEquals(27, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("bool", p.getText()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(9, loc.getColumnNr()); + assertEquals(42, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertNull(p.nextToken()); + + p.close(); + } + + public void testMergeAnchor() throws Exception + { + final String YAML = +"obj1: &objAnchor\n" ++" string: 'text'\n" ++" bool: True\n" ++"obj2:\n" ++" <<: *objAnchor\n" ++" int: 123\n" +; + JsonParser p = YAML_F.createParser(YAML); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("obj1", p.getText()); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + JsonLocation loc = p.currentTokenLocation(); + assertEquals(1, loc.getLineNr()); + assertEquals(7, loc.getColumnNr()); + assertEquals(6, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("text", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(11, loc.getColumnNr()); + assertEquals(27, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("bool", p.getText()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(9, loc.getColumnNr()); + assertEquals(42, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("obj2", p.getText()); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(5, loc.getLineNr()); + assertEquals(3, loc.getColumnNr()); + assertEquals(55, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("string", p.getText()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("text", p.getText()); + loc = p.currentTokenLocation(); + assertEquals(2, loc.getLineNr()); + assertEquals(11, loc.getColumnNr()); + assertEquals(27, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("bool", p.getText()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(3, loc.getLineNr()); + assertEquals(9, loc.getColumnNr()); + assertEquals(42, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("int", p.getText()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + loc = p.currentTokenLocation(); + assertEquals(6, loc.getLineNr()); + assertEquals(8, loc.getColumnNr()); + assertEquals(77, loc.getCharOffset()); + assertEquals(-1, loc.getByteOffset()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + assertNull(p.nextToken()); + + p.close(); + } +} From 6a37d333bb752bef1a3f6748f2470541e0e6c74d Mon Sep 17 00:00:00 2001 From: "Boettger, Heiko" Date: Thu, 12 Dec 2024 15:23:35 +0100 Subject: [PATCH 09/10] Refactored: use correct exception types --- .../yaml/YAMLAnchorReplayingParser.java | 23 +++++++++++-------- .../jackson/dataformat/yaml/YAMLParser.java | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java index a272380d..7f9e82f9 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java @@ -21,7 +21,10 @@ import org.yaml.snakeyaml.events.CollectionStartEvent; import org.yaml.snakeyaml.nodes.MappingNode; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; + import com.fasterxml.jackson.core.io.IOContext; /** @@ -92,12 +95,12 @@ public YAMLAnchorReplayingParser(IOContext ctxt, int parserFeatures, int formatF super(ctxt, parserFeatures, formatFeatures, loaderOptions, codec, reader); } - private void finishContext(AnchorContext context) { - if (referencedObjects.size() + 1 > MAX_REFS) throw new IllegalStateException("too many references in the document"); + private void finishContext(AnchorContext context) throws StreamConstraintsException { + if (referencedObjects.size() + 1 > MAX_REFS) throw new StreamConstraintsException("too many references in the document"); referencedObjects.put(context.anchor, context.events); if (!tokenStack.isEmpty()) { List events = tokenStack.peek().events; - if (events.size() + context.events.size() > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); + if (events.size() + context.events.size() > MAX_EVENTS) throw new StreamConstraintsException("too many events to replay"); events.addAll(context.events); } } @@ -124,7 +127,7 @@ protected Event filterEvent(Event event) { } @Override - protected Event getEvent() { + protected Event getEvent() throws IOException { while(!refEvents.isEmpty()) { Event event = filterEvent(trackDepth(refEvents.removeFirst())); if (event != null) return event; @@ -141,11 +144,11 @@ protected Event getEvent() { AliasEvent alias = (AliasEvent) event; List events = referencedObjects.get(alias.getAnchor()); if (events != null) { - if (refEvents.size() + events.size() > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); + if (refEvents.size() + events.size() > MAX_EVENTS) throw new StreamConstraintsException("too many events to replay"); refEvents.addAll(events); return refEvents.removeFirst(); } - throw new IllegalStateException("invalid alias " + alias.getAnchor()); + throw new JsonParseException("invalid alias " + alias.getAnchor()); } if (event instanceof NodeEvent) { @@ -154,7 +157,7 @@ protected Event getEvent() { AnchorContext context = new AnchorContext(anchor); context.events.add(event); if (event instanceof CollectionStartEvent) { - if (tokenStack.size() + 1 > MAX_ANCHORS) throw new IllegalStateException("too many anchors in the document"); + if (tokenStack.size() + 1 > MAX_ANCHORS) throw new StreamConstraintsException("too many anchors in the document"); tokenStack.push(context); } else { // directly store it @@ -170,17 +173,17 @@ protected Event getEvent() { // expect next node to be a map Event next = getEvent(); if (next instanceof MappingStartEvent) { - if (mergeStack.size() + 1 > MAX_MERGES) throw new IllegalStateException("too many merges in the document"); + if (mergeStack.size() + 1 > MAX_MERGES) throw new StreamConstraintsException("too many merges in the document"); mergeStack.push(globalDepth); return getEvent(); } - throw new IllegalStateException("found field '<<' but value isn't a map"); + throw new JsonParseException("found field '<<' but value isn't a map"); } } if (!tokenStack.isEmpty()) { AnchorContext context = tokenStack.peek(); - if (context.events.size() + 1 > MAX_EVENTS) throw new IllegalStateException("too many events to replay"); + if (context.events.size() + 1 > MAX_EVENTS) throw new StreamConstraintsException("too many events to replay"); context.events.add(event); if (event instanceof CollectionStartEvent) { ++context.depth; diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java index eed7a159..8c05a480 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java @@ -586,7 +586,7 @@ public JsonToken nextToken() throws IOException * * @since 2.18 */ - protected Event getEvent() { + protected Event getEvent() throws IOException { return _yamlParser.getEvent(); } From 356df23f4a8a4b838ecd244e22bdd33f9d3cd68f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Dec 2024 17:13:30 -0800 Subject: [PATCH 10/10] Add release notes, cleaned some warnings, added `@since` annotations --- release-notes/CREDITS-2.x | 3 +++ release-notes/VERSION-2.x | 4 ++- .../yaml/YAMLAnchorReplayingFactory.java | 24 ++++++++++++------ .../yaml/YAMLAnchorReplayingParser.java | 25 ++++++------------- .../jackson/dataformat/yaml/YAMLParser.java | 5 +++- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index d6339e68..86bf1c68 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -276,6 +276,9 @@ Heiko Boettger (@HeikoBoettger) * Contributed #482: (yaml) Allow passing `ParserImpl` by a subclass or overwrite the events (2.18.0) +* Contributed #502: (yaml) Add an optional extended parser subclass (`YAMLAnchorReplayingFactory`) + able to inline anchors + (2.19.0) Burdyug Pavel (@Pavel38l) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index f7fda5ef..ffb8c7bc 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -16,7 +16,9 @@ Active Maintainers: 2.19.0 (not yet released) -- +#502: Add an optional extended parser subclass (`YAMLAnchorReplayingFactory`) + able to inline anchors + (contributed by Heiko B) 2.18.2 (27-Nov-2024) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java index 01dbf784..d1fb9b13 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingFactory.java @@ -10,9 +10,14 @@ import com.fasterxml.jackson.core.io.IOContext; /** - * A subclass of YAMLFactory with the only purpose to replace the YAMLParser by the YAMLAnchorReplayingParser subclass + * A subclass of YAMLFactory with the only purpose to replace the YAMLParser by + * the YAMLAnchorReplayingParser subclass. + * + * @since 2.19 */ public class YAMLAnchorReplayingFactory extends YAMLFactory { + private static final long serialVersionUID = 1L; + public YAMLAnchorReplayingFactory() { super(); } @@ -31,32 +36,37 @@ protected YAMLAnchorReplayingFactory(YAMLFactoryBuilder b) { @Override public YAMLAnchorReplayingFactory copy() { - this._checkInvalidCopy(YAMLAnchorReplayingFactory.class); + _checkInvalidCopy(YAMLAnchorReplayingFactory.class); return new YAMLAnchorReplayingFactory(this, (ObjectCodec) null); } @Override protected Object readResolve() { - return new YAMLAnchorReplayingFactory(this, this._objectCodec); + return new YAMLAnchorReplayingFactory(this, _objectCodec); } @Override protected YAMLParser _createParser(InputStream input, IOContext ctxt) throws IOException { - return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(input, (JsonEncoding) null, ctxt)); + return new YAMLAnchorReplayingParser(ctxt, _parserFeatures, _yamlParserFeatures, + _loaderOptions, _objectCodec, + _createReader(input, (JsonEncoding) null, ctxt)); } @Override protected YAMLParser _createParser(Reader r, IOContext ctxt) throws IOException { - return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, r); + return new YAMLAnchorReplayingParser(ctxt, _parserFeatures, _yamlParserFeatures, + _loaderOptions, _objectCodec, r); } @Override protected YAMLParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable) throws IOException { - return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, new CharArrayReader(data, offset, len)); + return new YAMLAnchorReplayingParser(ctxt, _parserFeatures, _yamlParserFeatures, + _loaderOptions, _objectCodec, new CharArrayReader(data, offset, len)); } @Override protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException { - return new YAMLAnchorReplayingParser(ctxt, this._parserFeatures, this._yamlParserFeatures, this._loaderOptions, this._objectCodec, this._createReader(data, offset, len, (JsonEncoding) null, ctxt)); + return new YAMLAnchorReplayingParser(ctxt, _parserFeatures, _yamlParserFeatures, + _loaderOptions, _objectCodec, _createReader(data, offset, len, (JsonEncoding) null, ctxt)); } } diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java index 7f9e82f9..6782725d 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLAnchorReplayingParser.java @@ -3,38 +3,27 @@ import java.io.Reader; import java.io.IOException; -import java.util.ArrayList; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; +import java.util.*; import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.events.AliasEvent; -import org.yaml.snakeyaml.events.Event; -import org.yaml.snakeyaml.events.MappingEndEvent; -import org.yaml.snakeyaml.events.MappingStartEvent; -import org.yaml.snakeyaml.events.NodeEvent; -import org.yaml.snakeyaml.events.ScalarEvent; -import org.yaml.snakeyaml.events.CollectionEndEvent; -import org.yaml.snakeyaml.events.CollectionStartEvent; -import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.events.*; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.exc.StreamConstraintsException; - import com.fasterxml.jackson.core.io.IOContext; /** * A parser that remembers the events of anchored parts in yaml and repeats them * to inline these parts when an alias if found instead of only returning an alias. - * + *

* Note: this overwrites the getEvent() since the base `super.nextToken()` manages to much state and * it seems to be much simpler to re-emit the events. + * + * @since 2.19 */ -public class YAMLAnchorReplayingParser extends YAMLParser { +public class YAMLAnchorReplayingParser extends YAMLParser +{ private static class AnchorContext { public final String anchor; public final List events = new ArrayList<>(); diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java index 8c05a480..49ceff10 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java @@ -580,9 +580,12 @@ public JsonToken nextToken() throws IOException /** * Since the parserImpl cannot be replaced allow subclasses to at least be able to * influence the events being consumed. - * + *

* A particular use case is working around the lack of anchor and alias support to * emit additional events. + *

+ * NOTE: since 2.18, declared to throw {@link IOException} to allow sub-classes + * to do so. * * @since 2.18 */