-
-
Notifications
You must be signed in to change notification settings - Fork 150
Add an optional extended parser subclass (YAMLAnchorReplayingFactory) able to inline anchors (2.x ONLY -- unusual)
#502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cowtowncoder
merged 14 commits into
FasterXML:2.19
from
HeikoBoettger-KarlStorz:improved-yaml-anchors-support
Dec 21, 2024
Merged
Changes from 4 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
3b277c5
Added: YAMLParserExt adding extended yaml parsing with anchor support
HeikoBoettger-KarlStorz a1140f3
Added: introduced a factory to create YAMLParserExt
HeikoBoettger-KarlStorz 9fc524c
Merge branch '2.19' into improved-yaml-anchors-support
cowtowncoder 6d6fbee
Merge branch '2.19' into improved-yaml-anchors-support
cowtowncoder e94ea2a
Fixed: resolved review findings using ArrayDeque insteadof Stack
HeikoBoettger-KarlStorz 773e3a4
Fixed: added missing `final` on some members
HeikoBoettger-KarlStorz 7dd8566
Renamed: java-file `YAML*Ext.java` to new class names `YAMLAnchorRepl…
HeikoBoettger-KarlStorz b547243
Refactored: renamed classes `YAML*Ext.java` to `YAMLAnchorReplaying*.…
HeikoBoettger-KarlStorz dd36d1d
Merge branch '2.19' into improved-yaml-anchors-support
HeikoBoettger-KarlStorz ffdafed
Added: introduced constants for defining the upper limit
HeikoBoettger-KarlStorz 19f6a1e
Added: implemented a test for the YAMLAnchroReplayingParser
HeikoBoettger-KarlStorz 81fb556
Merge branch '2.19' into improved-yaml-anchors-support
cowtowncoder 6a37d33
Refactored: use correct exception types
HeikoBoettger-KarlStorz 356df23
Add release notes, cleaned some warnings, added `@since` annotations
cowtowncoder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryExt.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)); | ||
| } | ||
| } |
170 changes: 170 additions & 0 deletions
170
yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParserExt.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Event> 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<Integer> mergeStack = new Stack<>(); | ||
|
|
||
| /** | ||
| * Collects nested anchor definitions | ||
| */ | ||
| private Stack<AnchorContext> tokenStack = new Stack<>(); | ||
|
|
||
| /** | ||
| * Keeps track of the last sequentially found definition of each anchor | ||
| */ | ||
| private Map<String, List<Event>> referencedObjects = new HashMap<>(); | ||
|
|
||
| /** | ||
| * Keeps track of events that have been insert when processing alias | ||
| */ | ||
| private ArrayDeque<Event> refEvents = new ArrayDeque<>(); | ||
|
|
||
| /** | ||
| * keeps track of the global depth of nested collections | ||
| */ | ||
| private int global_depth = 0; | ||
HeikoBoettger-KarlStorz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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()) {; | ||
HeikoBoettger-KarlStorz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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<Event> 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; | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.