From deb89f0e31926fa6638fc06a20a948dd7ca27201 Mon Sep 17 00:00:00 2001 From: Zach Levis Date: Thu, 22 Oct 2020 20:36:23 -0700 Subject: [PATCH] yaml: update for core changes --- .../configurate/yaml/ConfigurateScanner.java | 2 +- .../configurate/yaml/ScalarStyle.java | 15 ++- .../spongepowered/configurate/yaml/Tag.java | 68 ++++++++--- .../configurate/yaml/TagRepository.java | 16 ++- .../configurate/yaml/Yaml11Tags.java | 108 ++++++++++++------ .../yaml/YamlConfigurationLoader.java | 15 +++ .../configurate/yaml/YamlParser.java | 52 ++++++--- .../configurate/yaml/YamlVisitor.java | 32 +++--- .../configurate/yaml/YamlTest.java | 16 +++ 9 files changed, 238 insertions(+), 86 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java index bbfb7227c..371204b9c 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java @@ -183,7 +183,7 @@ final class ConfigurateScanner implements Scanner { // Configurate: rename + pac // 32-bit Unicode (Supplementary characters are supported) ESCAPE_CODES.put(Character.valueOf('U'), 8); } - private final StreamReader reader; + final StreamReader reader; // Configurate: private -> package-private // Had we reached the end of the stream? private boolean done = false; diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java index a6be0788f..e1b5e47b0 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java @@ -19,7 +19,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.yaml.snakeyaml.DumperOptions; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; /** @@ -27,14 +27,25 @@ */ public enum ScalarStyle { + /** + * A double-quoted string. + * + *

"hello world"

+ */ DOUBLE_QUOTED(DumperOptions.ScalarStyle.DOUBLE_QUOTED), + + /** + * A single-quoted string. + * + *

'hello world'

+ */ SINGLE_QUOTED(DumperOptions.ScalarStyle.SINGLE_QUOTED), UNQUOTED(DumperOptions.ScalarStyle.PLAIN), FOLDED(DumperOptions.ScalarStyle.FOLDED), LITERAL(DumperOptions.ScalarStyle.LITERAL) ; - private static final Map BY_SNAKE = new HashMap<>(); + private static final Map BY_SNAKE = new EnumMap<>(DumperOptions.ScalarStyle.class); private final DumperOptions.ScalarStyle snake; ScalarStyle(final DumperOptions.ScalarStyle snake) { diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java index 9d1e82426..ae77ade01 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java @@ -31,6 +31,11 @@ @AutoValue public abstract class Tag { + /** + * Create a new builder for a {@link Tag}. + * + * @return a new builder + */ public static Tag.Builder builder() { return new AutoValue_Tag.Builder(); } @@ -43,14 +48,14 @@ public static Tag.Builder builder() { * * @return tag uri, with `tag:` schema */ - public abstract URI getUri(); + public abstract URI uri(); /** * The native type that maps to this tag. * * @return native type for tag */ - public abstract Type getNativeType(); + public abstract Type nativeType(); /** * Pattern to test scalar values against when resolving this tag. @@ -58,38 +63,73 @@ public static Tag.Builder builder() { * @return match pattern * @apiNote See ยง3.3.2 of YAML 1.1 spec */ - public abstract Pattern getTargetPattern(); + public abstract Pattern targetPattern(); /** * Whether this tag is a global tag with a full namespace or a local one. * * @return if this is a global tag */ - public final boolean isGlobal() { - return getUri().getScheme().equals("tag"); + public final boolean global() { + return uri().getScheme().equals("tag"); } + /** + * A builder for {@link Tag Tags}. + */ @AutoValue.Builder public abstract static class Builder { - public abstract Builder setUri(URI url); - - public final Builder setUri(final String tagUrl) { + /** + * Set the URI used to refer to the tag. + * + * @param url canonical tag URI + * @return this builder + */ + public abstract Builder uri(URI url); + + /** + * Set the URI used to refer to the tag, parsing a new URL from + * the argument. + * + * @param tagUrl canonical tag URI + * @return this builder + */ + public final Builder uri(final String tagUrl) { try { if (tagUrl.startsWith("!")) { - return this.setUri(new URI(tagUrl.substring(1))); + return this.uri(new URI(tagUrl.substring(1))); } else { - return this.setUri(new URI(tagUrl)); + return this.uri(new URI(tagUrl)); } } catch (final URISyntaxException e) { throw new RuntimeException(e); } } - public abstract Builder setNativeType(Type type); - - public abstract Builder setTargetPattern(Pattern targetPattern); - + /** + * The Java type that will be used to represent this value in the node + * structure. + * + * @param type type for the value + * @return this builder + */ + public abstract Builder nativeType(Type type); + + /** + * Pattern to match an undefined scalar string to this tag as an + * implicit tag. + * + * @param targetPattern pattern to match + * @return this builder + */ + public abstract Builder targetPattern(Pattern targetPattern); + + /** + * Create a new tag from the provided parameters. + * + * @return a new tag + */ public abstract Tag build(); } diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java index 148f6d9a7..724f07c6e 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java @@ -24,13 +24,15 @@ import java.util.List; import java.util.Map; +/** + * A collection of tags that are understood when reading a document. + */ public final class TagRepository { private final List tags; private final Map, Tag> byErasedType; private final Map byName; - /** * Create a new tag repository. * @@ -45,19 +47,25 @@ public static TagRepository of(final List tags) { this.tags = tags; this.byErasedType = UnmodifiableCollections.buildMap(map -> { for (final Tag tag : this.tags) { - map.put(erase(tag.getNativeType()), tag); + map.put(erase(tag.nativeType()), tag); } }); this.byName = UnmodifiableCollections.buildMap(map -> { for (final Tag tag : this.tags) { - map.put(tag.getUri().toString(), tag); + map.put(tag.uri().toString(), tag); } }); } + /** + * Determine the implicit tag for a scalar value. + * + * @param scalar scalar to test + * @return the first matching tag + */ public @Nullable Tag forInput(final String scalar) { for (final Tag tag : this.tags) { - if (tag.getTargetPattern().matcher(scalar).matches()) { + if (tag.targetPattern().matcher(scalar).matches()) { return tag; } } diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java index 522a4f18e..4dee93a03 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java @@ -34,73 +34,113 @@ private static String yamlOrg(final String specific) { return "tag:yaml.org,2002:" + specific; } - // https://yaml.org/type/binary.html + /** + * A binary data tag. + * + * @see tag:yaml.org,2002:binary + */ public static final Tag BINARY = Tag.builder() - .setUri(yamlOrg("binary")) - .setNativeType(byte[].class) - .setTargetPattern(Pattern.compile("base64 TODO")) + .uri(yamlOrg("binary")) + .nativeType(byte[].class) + .targetPattern(Pattern.compile("base64 TODO")) .build(); - // https://yaml.org/type/bool.html - // Canonically these are y|n in YAML 1.1, but because YAML 1.2 moves to - // true|false only, we'll just use those + /** + * A boolean value. + * + * @implNote Canonically, these are y|n in YAML 1.1, but because YAML 1.2 + * will only support true|false, we will treat those as the default + * output format. + * @see tag:yaml.org,2002:bool + */ public static final Tag BOOL = Tag.builder() - .setUri(yamlOrg("bool")) - .setNativeType(Boolean.class) - .setTargetPattern(Pattern.compile("y|Y|yes|Yes|YES|n|N|no|No|NO" + .uri(yamlOrg("bool")) + .nativeType(Boolean.class) + .targetPattern(Pattern.compile("y|Y|yes|Yes|YES|n|N|no|No|NO" + "|true|True|TRUE|false|False|FALSE" + "|on|On|ON|off|Off|OFF")) .build(); - // https://yaml.org/type/float.html + /** + * A floating-point number. + * + * @see tag:yaml.org,2002:float + */ public static final Tag FLOAT = Tag.builder() - .setUri(yamlOrg("float")) - .setNativeType(Double.class) - .setTargetPattern(Pattern.compile("[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?" // base 10 + .uri(yamlOrg("float")) + .nativeType(Double.class) + .targetPattern(Pattern.compile("[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?" // base 10 + "|[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\\.[0-9]*" // base 60 + "|[-+]?\\.(inf|Inf|INF)" // infinity + "|\\.(nan|NaN|NAN)")) // not a number .build(); - // https://yaml.org/type/int.html + /** + * An integer. + * + * @see tag:yaml.org,2002:int + */ public static final Tag INT = Tag.builder() - .setUri(yamlOrg("int")) - .setNativeType(Long.class) - .setTargetPattern(Pattern.compile("[-+]?0b[0-1_]+" // base 2 + .uri(yamlOrg("int")) + .nativeType(Long.class) + .targetPattern(Pattern.compile("[-+]?0b[0-1_]+" // base 2 + "|[-+]?0[0-7_]+" // base 8 + "|[-+]?(0|[1-9][0-9_]*)" // base 10 + "|[-+]?0x[0-9a-fA-F_]+" // base 16 + "|[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+")) // base 60 .build(); - // https://yaml.org/type/merge.html + /** + * A mapping merge. + * + *

This will not be supported in Configurate until reference-type nodes + * are fully implemented.

+ * + * @see tag:yaml.org,2002:merge + */ public static final Tag MERGE = Tag.builder() - .setUri(yamlOrg("merge")) - .setNativeType(ConfigurationNode.class) - .setTargetPattern(Pattern.compile("<<")) + .uri(yamlOrg("merge")) + .nativeType(ConfigurationNode.class) + .targetPattern(Pattern.compile("<<")) .build(); - // https://yaml.org/type/null.html + /** + * The value {@code null}. + * + *

Because Configurate has no distinction between a node with a + * {@code null} value, and a node that does not exist, this tag will most + * likely never be encountered in an in-memory representation.

+ * + * @see tag:yaml.org,2002:null + */ public static final Tag NULL = Tag.builder() - .setUri(yamlOrg("null")) - .setNativeType(Void.class) - .setTargetPattern(Pattern.compile("~" + .uri(yamlOrg("null")) + .nativeType(Void.class) + .targetPattern(Pattern.compile("~" + "|null|Null|NULL" + "|$")) .build(); - // https://yaml.org/type/str.html + /** + * Any string. + * + * @see tag:yaml.org,2002:str + */ public static final Tag STR = Tag.builder() - .setUri(yamlOrg("str")) - .setNativeType(String.class) - .setTargetPattern(Pattern.compile(".+")) // empty scalar is NULL + .uri(yamlOrg("str")) + .nativeType(String.class) + .targetPattern(Pattern.compile(".+")) // empty scalar is NULL .build(); - // https://yaml.org/type/timestamp.html + /** + * A timestamp, containing date, time, and timezone. + * + * @see tag:yaml.org,2002:timestamp + */ public static final Tag TIMESTAMP = Tag.builder() - .setUri(yamlOrg("timestamp")) - .setNativeType(ZonedDateTime.class) - .setTargetPattern(Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}" // YYYY-MM-DD + .uri(yamlOrg("timestamp")) + .nativeType(ZonedDateTime.class) + .targetPattern(Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}" // YYYY-MM-DD + "|[0-9]{4}" // YYYY + "-[0-9]{1,2}" // month + "-[0-9]{1,2}" // day diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index 8072802f1..343c73f69 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -48,10 +48,25 @@ */ public final class YamlConfigurationLoader extends AbstractConfigurationLoader { + /** + * The identifier for a YAML anchor that can be used to refer to the node + * this hint is set on. + */ public static final RepresentationHint ANCHOR_ID = RepresentationHint.of("anchor-id", String.class); + /** + * The YAML scalar style this node should attempt to use. + * + *

If the chosen scalar style would produce syntactically invalid YAML, a + * valid one will replace it.

+ */ public static final RepresentationHint SCALAR_STYLE = RepresentationHint.of("scalar-style", ScalarStyle.class); + /** + * The YAML node style to use for collection nodes. A {@code null} value + * will instruct the emitter to fall back to the + * {@link Builder#nodeStyle()} setting. + */ public static final RepresentationHint NODE_STYLE = RepresentationHint.of("node-style", NodeStyle.class); /** diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java index 273adcad7..74a7b9199 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java @@ -16,18 +16,21 @@ */ package org.spongepowered.configurate.yaml; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.configurate.BasicConfigurationNode; import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary; +import org.spongepowered.configurate.ConfigurateException; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationNodeFactory; +import org.spongepowered.configurate.loader.ParsingException; import org.yaml.snakeyaml.events.AliasEvent; import org.yaml.snakeyaml.events.CollectionStartEvent; import org.yaml.snakeyaml.events.Event; import org.yaml.snakeyaml.events.NodeEvent; import org.yaml.snakeyaml.events.ScalarEvent; import org.yaml.snakeyaml.parser.ParserImpl; +import org.yaml.snakeyaml.reader.StreamReader; -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -62,28 +65,28 @@ private ConfigurateScanner scanner() { return (ConfigurateScanner) this.scanner; } - Event requireEvent(final Event.ID type) throws IOException { + Event requireEvent(final Event.ID type) throws ParsingException { final Event next = peekEvent(); if (!next.is(type)) { - throw new IOException("Expected next event of type" + type + " but was " + next.getEventId()); + throw makeError("Expected next event of type" + type + " but was " + next.getEventId(), null); } return this.getEvent(); } @SuppressWarnings("unchecked") - T requireEvent(final Event.ID type, final Class clazz) throws IOException { + T requireEvent(final Event.ID type, final Class clazz) throws ParsingException { final Event next = peekEvent(); if (!next.is(type)) { - throw new IOException("Expected next event of type" + type + " but was " + next.getEventId()); + throw makeError("Expected next event of type" + type + " but was " + next.getEventId(), null); } if (!clazz.isInstance(next)) { - throw new IOException("Expected event of type " + clazz + " but got a " + next.getClass()); + throw makeError("Expected event of type " + clazz + " but got a " + next.getClass(), null); } return (T) this.getEvent(); } - public Stream stream(final ConfigurationNodeFactory factory) throws IOException { + public Stream stream(final ConfigurationNodeFactory factory) throws ParsingException { requireEvent(Event.ID.StreamStart); return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator() { @Override @@ -100,31 +103,34 @@ public N next() { final N node = factory.createNode(); document(node); return node; - } catch (final IOException e) { + } catch (final ConfigurateException e) { throw new RuntimeException(e); // TODO } } }, Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.NONNULL), false); } - public void singleDocumentStream(final ConfigurationNode node) throws IOException { + public void singleDocumentStream(final ConfigurationNode node) throws ParsingException { requireEvent(Event.ID.StreamStart); document(node); requireEvent(Event.ID.StreamEnd); } - public void document(final ConfigurationNode node) throws IOException { + public void document(final ConfigurationNode node) throws ParsingException { requireEvent(Event.ID.DocumentStart); this.scanner().setCaptureComments(node instanceof CommentedConfigurationNodeIntermediary); try { value(node); + } catch (final ConfigurateException ex) { + ex.initPath(node::path); + throw ex; } finally { this.aliases.clear(); } requireEvent(Event.ID.DocumentEnd); } - void value(final ConfigurationNode node) throws IOException { + void value(final ConfigurationNode node) throws ParsingException { // We have to capture the comment before we peek ahead // peeking ahead will start consuming the next event and its comments applyComment(node); @@ -157,17 +163,17 @@ void value(final ConfigurationNode node) throws IOException { alias(node); break; default: - throw new IOException("Unexpected event type " + peekEvent().getEventId()); + throw makeError(node, "Unexpected event type " + peekEvent().getEventId(), null); } } - void scalar(final ConfigurationNode node) throws IOException { + void scalar(final ConfigurationNode node) throws ParsingException { final ScalarEvent scalar = requireEvent(Event.ID.Scalar, ScalarEvent.class); node.hint(YamlConfigurationLoader.SCALAR_STYLE, ScalarStyle.fromSnakeYaml(scalar.getScalarStyle())); node.raw(scalar.getValue()); // TODO: tags and value types } - void mapping(final ConfigurationNode node) throws IOException { + void mapping(final ConfigurationNode node) throws ParsingException { requireEvent(Event.ID.MappingStart); node.raw(Collections.emptyMap()); @@ -181,7 +187,7 @@ void mapping(final ConfigurationNode node) throws IOException { } final ConfigurationNode child = node.node(keyHolder.raw()); if (!child.virtual()) { // duplicate keys are forbidden (3.2.1.3) - throw new IOException("Duplicate key '" + child.key() + "' encountered!"); + throw makeError(node, "Duplicate key '" + child.key() + "' encountered!", null); } value(node.node(child)); } @@ -189,7 +195,7 @@ void mapping(final ConfigurationNode node) throws IOException { requireEvent(Event.ID.MappingEnd); } - void sequence(final ConfigurationNode node) throws IOException { + void sequence(final ConfigurationNode node) throws ParsingException { requireEvent(Event.ID.SequenceStart); node.raw(Collections.emptyList()); @@ -200,14 +206,24 @@ void sequence(final ConfigurationNode node) throws IOException { requireEvent(Event.ID.SequenceEnd); } - void alias(final ConfigurationNode node) throws IOException { + void alias(final ConfigurationNode node) throws ParsingException { final AliasEvent event = requireEvent(Event.ID.Alias, AliasEvent.class); final ConfigurationNode target = this.aliases.get(event.getAnchor()); if (target == null) { - throw new IOException("Unknown anchor '" + event.getAnchor() + "'"); + throw makeError(node, "Unknown anchor '" + event.getAnchor() + "'", null); } node.from(target); // TODO: Reference node types node.hint(YamlConfigurationLoader.ANCHOR_ID, null); // don't duplicate alias } + private ParsingException makeError(final @Nullable String message, final @Nullable Throwable error) { + final StreamReader reader = scanner().reader; + return new ParsingException(reader.getLine(), reader.getColumn(), null, message, error); + } + + private ParsingException makeError(final ConfigurationNode node, final @Nullable String message, final @Nullable Throwable error) { + final StreamReader reader = scanner().reader; + return new ParsingException(node, reader.getLine(), reader.getColumn(), null, message, error); + } + } diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java index 29535bb3c..68ad6c82f 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java @@ -17,10 +17,12 @@ package org.spongepowered.configurate.yaml; import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationVisitor; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.emitter.Emitter; +import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.events.DocumentEndEvent; import org.yaml.snakeyaml.events.DocumentStartEvent; import org.yaml.snakeyaml.events.Event; @@ -39,7 +41,7 @@ import java.io.IOException; -final class YamlVisitor implements ConfigurationVisitor { +final class YamlVisitor implements ConfigurationVisitor { private static final StreamStartEvent STREAM_START = new StreamStartEvent(null, null); private static final StreamEndEvent STREAM_END = new StreamEndEvent(null, null); @@ -58,12 +60,12 @@ final class YamlVisitor implements ConfigurationVisitor