aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/yaml
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/yaml')
-rw-r--r--src/main/java/org/yaml/snakeyaml/DumperOptions.java774
-rw-r--r--src/main/java/org/yaml/snakeyaml/LoaderOptions.java149
-rw-r--r--src/main/java/org/yaml/snakeyaml/TypeDescription.java472
-rw-r--r--src/main/java/org/yaml/snakeyaml/Yaml.java1253
-rw-r--r--src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java177
-rw-r--r--src/main/java/org/yaml/snakeyaml/comments/CommentLine.java65
-rw-r--r--src/main/java/org/yaml/snakeyaml/comments/CommentType.java23
-rw-r--r--src/main/java/org/yaml/snakeyaml/composer/Composer.java498
-rw-r--r--src/main/java/org/yaml/snakeyaml/composer/ComposerException.java27
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java47
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java895
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/Construct.java65
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/Constructor.java1200
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java37
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java47
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java23
-rw-r--r--src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java949
-rw-r--r--src/main/java/org/yaml/snakeyaml/emitter/Emitable.java22
-rw-r--r--src/main/java/org/yaml/snakeyaml/emitter/Emitter.java2760
-rw-r--r--src/main/java/org/yaml/snakeyaml/emitter/EmitterException.java27
-rwxr-xr-xsrc/main/java/org/yaml/snakeyaml/emitter/EmitterState.java23
-rw-r--r--src/main/java/org/yaml/snakeyaml/emitter/ScalarAnalysis.java89
-rw-r--r--src/main/java/org/yaml/snakeyaml/env/EnvScalarConstructor.java132
-rw-r--r--src/main/java/org/yaml/snakeyaml/error/Mark.java245
-rw-r--r--src/main/java/org/yaml/snakeyaml/error/MarkedYAMLException.java158
-rw-r--r--src/main/java/org/yaml/snakeyaml/error/MissingEnvironmentVariableException.java24
-rw-r--r--src/main/java/org/yaml/snakeyaml/error/YAMLException.java39
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/AliasEvent.java34
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/CollectionEndEvent.java24
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/CollectionStartEvent.java124
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/CommentEvent.java70
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/DocumentEndEvent.java43
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/DocumentStartEvent.java96
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/Event.java125
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/ImplicitTuple.java78
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/MappingEndEvent.java34
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/MappingStartEvent.java55
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/NodeEvent.java59
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/ScalarEvent.java189
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/SequenceEndEvent.java34
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/SequenceStartEvent.java54
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/StreamEndEvent.java38
-rw-r--r--src/main/java/org/yaml/snakeyaml/events/StreamStartEvent.java37
-rw-r--r--src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactConstructor.java371
-rw-r--r--src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactData.java69
-rw-r--r--src/main/java/org/yaml/snakeyaml/extensions/compactnotation/PackageCompactConstructor.java49
-rw-r--r--src/main/java/org/yaml/snakeyaml/external/biz/base64Coder/Base64Coder.java498
-rw-r--r--src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/Escaper.java147
-rw-r--r--src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/PercentEscaper.java472
-rw-r--r--src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/UnicodeEscaper.java878
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/BeanAccess.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/FieldProperty.java78
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/GenericProperty.java121
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/MethodProperty.java154
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/MissingProperty.java76
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/Property.java133
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/PropertySubstitute.java253
-rw-r--r--src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java273
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/AnchorNode.java45
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/CollectionNode.java98
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/MappingNode.java186
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/Node.java345
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/NodeId.java22
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/NodeTuple.java73
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/ScalarNode.java145
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/SequenceNode.java111
-rw-r--r--src/main/java/org/yaml/snakeyaml/nodes/Tag.java277
-rw-r--r--src/main/java/org/yaml/snakeyaml/parser/Parser.java82
-rw-r--r--src/main/java/org/yaml/snakeyaml/parser/ParserException.java51
-rw-r--r--src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java1512
-rw-r--r--src/main/java/org/yaml/snakeyaml/parser/Production.java29
-rw-r--r--src/main/java/org/yaml/snakeyaml/parser/VersionTagsTuple.java52
-rw-r--r--src/main/java/org/yaml/snakeyaml/reader/ReaderException.java79
-rw-r--r--src/main/java/org/yaml/snakeyaml/reader/StreamReader.java394
-rw-r--r--src/main/java/org/yaml/snakeyaml/reader/UnicodeReader.java187
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java319
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/Represent.java42
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/Representer.java410
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java737
-rw-r--r--src/main/java/org/yaml/snakeyaml/resolver/Resolver.java225
-rw-r--r--src/main/java/org/yaml/snakeyaml/resolver/ResolverTuple.java68
-rw-r--r--src/main/java/org/yaml/snakeyaml/scanner/Constant.java110
-rw-r--r--src/main/java/org/yaml/snakeyaml/scanner/Scanner.java87
-rw-r--r--src/main/java/org/yaml/snakeyaml/scanner/ScannerException.java83
-rw-r--r--src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java4438
-rw-r--r--src/main/java/org/yaml/snakeyaml/scanner/SimpleKey.java95
-rw-r--r--src/main/java/org/yaml/snakeyaml/serializer/AnchorGenerator.java20
-rw-r--r--src/main/java/org/yaml/snakeyaml/serializer/NumberAnchorGenerator.java47
-rw-r--r--src/main/java/org/yaml/snakeyaml/serializer/Serializer.java323
-rw-r--r--src/main/java/org/yaml/snakeyaml/serializer/SerializerException.java27
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/AliasToken.java46
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/AnchorToken.java46
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/BlockEndToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/BlockEntryToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/BlockMappingStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/BlockSequenceStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java52
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/DirectiveToken.java66
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/DocumentEndToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/DocumentStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/FlowEntryToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/FlowMappingEndToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/FlowMappingStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceEndToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/KeyToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/ScalarToken.java79
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/StreamEndToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/StreamStartToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/TagToken.java46
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/TagTuple.java47
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/Token.java103
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/ValueToken.java32
-rw-r--r--src/main/java/org/yaml/snakeyaml/tokens/WhitespaceToken.java37
-rw-r--r--src/main/java/org/yaml/snakeyaml/util/ArrayStack.java51
-rw-r--r--src/main/java/org/yaml/snakeyaml/util/ArrayUtils.java109
-rw-r--r--src/main/java/org/yaml/snakeyaml/util/EnumUtils.java38
-rw-r--r--src/main/java/org/yaml/snakeyaml/util/PlatformFeatureDetector.java27
-rw-r--r--src/main/java/org/yaml/snakeyaml/util/UriEncoder.java81
119 files changed, 14823 insertions, 12122 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/DumperOptions.java b/src/main/java/org/yaml/snakeyaml/DumperOptions.java
index 07621981..48b5e096 100644
--- a/src/main/java/org/yaml/snakeyaml/DumperOptions.java
+++ b/src/main/java/org/yaml/snakeyaml/DumperOptions.java
@@ -1,405 +1,487 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml;
import java.util.Map;
import java.util.TimeZone;
-
import org.yaml.snakeyaml.emitter.Emitter;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.serializer.AnchorGenerator;
import org.yaml.snakeyaml.serializer.NumberAnchorGenerator;
public class DumperOptions {
- /**
- * YAML provides a rich set of scalar styles. Block scalar styles include
- * the literal style and the folded style; flow scalar styles include the
- * plain style and two quoted styles, the single-quoted style and the
- * double-quoted style. These styles offer a range of trade-offs between
- * expressive power and readability.
- *
- * @see <a href="http://yaml.org/spec/1.1/#id903915">Chapter 9. Scalar
- * Styles</a>
- * @see <a href="http://yaml.org/spec/1.1/#id858081">2.3. Scalars</a>
- */
- public enum ScalarStyle {
- DOUBLE_QUOTED(Character.valueOf('"')), SINGLE_QUOTED(Character.valueOf('\'')), LITERAL(
- Character.valueOf('|')), FOLDED(Character.valueOf('>')), PLAIN(null);
- private Character styleChar;
-
- private ScalarStyle(Character style) {
- this.styleChar = style;
- }
-
- public Character getChar() {
- return styleChar;
- }
-
- @Override
- public String toString() {
- return "Scalar style: '" + styleChar + "'";
- }
-
- public static ScalarStyle createStyle(Character style) {
- if (style == null) {
- return PLAIN;
- } else {
- switch (style) {
- case '"':
- return DOUBLE_QUOTED;
- case '\'':
- return SINGLE_QUOTED;
- case '|':
- return LITERAL;
- case '>':
- return FOLDED;
- default:
- throw new YAMLException("Unknown scalar style character: " + style);
- }
- }
- }
- }
-
- /**
- * Block styles use indentation to denote nesting and scope within the
- * document. In contrast, flow styles rely on explicit indicators to denote
- * nesting and scope.
- *
- * @see <a href="http://www.yaml.org/spec/current.html#id2509255">3.2.3.1.
- * Node Styles (http://yaml.org/spec/1.1)</a>
- */
- public enum FlowStyle {
- FLOW(Boolean.TRUE), BLOCK(Boolean.FALSE), AUTO(null);
-
- private Boolean styleBoolean;
-
- private FlowStyle(Boolean flowStyle) {
- styleBoolean = flowStyle;
- }
-
- public Boolean getStyleBoolean() {
- return styleBoolean;
- }
-
- @Override
- public String toString() {
- return "Flow style: '" + styleBoolean + "'";
- }
- }
-
- /**
- * Platform dependent line break.
- */
- public enum LineBreak {
- WIN("\r\n"), MAC("\r"), UNIX("\n");
- private String lineBreak;
-
- private LineBreak(String lineBreak) {
- this.lineBreak = lineBreak;
- }
-
- public String getString() {
- return lineBreak;
- }
-
- @Override
- public String toString() {
- return "Line break: " + name();
- }
-
- public static LineBreak getPlatformLineBreak() {
- String platformLineBreak = System.getProperty("line.separator");
- for (LineBreak lb : values()) {
- if (lb.lineBreak.equals(platformLineBreak)) {
- return lb;
- }
- }
- return LineBreak.UNIX;
+ /**
+ * YAML provides a rich set of scalar styles. Block scalar styles include the literal style and
+ * the folded style; flow scalar styles include the plain style and two quoted styles, the
+ * single-quoted style and the double-quoted style. These styles offer a range of trade-offs
+ * between expressive power and readability.
+ *
+ * @see <a href="http://yaml.org/spec/1.1/#id903915">Chapter 9. Scalar Styles</a>
+ * @see <a href="http://yaml.org/spec/1.1/#id858081">2.3. Scalars</a>
+ */
+ public enum ScalarStyle {
+ DOUBLE_QUOTED('"'), SINGLE_QUOTED('\''), LITERAL('|'), FOLDED('>'), PLAIN(null);
+
+ private final Character styleChar;
+
+ ScalarStyle(Character style) {
+ this.styleChar = style;
+ }
+
+ public Character getChar() {
+ return styleChar;
+ }
+
+ @Override
+ public String toString() {
+ return "Scalar style: '" + styleChar + "'";
+ }
+
+ public static ScalarStyle createStyle(Character style) {
+ if (style == null) {
+ return PLAIN;
+ } else {
+ switch (style) {
+ case '"':
+ return DOUBLE_QUOTED;
+ case '\'':
+ return SINGLE_QUOTED;
+ case '|':
+ return LITERAL;
+ case '>':
+ return FOLDED;
+ default:
+ throw new YAMLException("Unknown scalar style character: " + style);
}
+ }
}
+ }
- /**
- * Specification version. Currently supported 1.0 and 1.1
- */
- public enum Version {
- V1_0(new Integer[] { 1, 0 }), V1_1(new Integer[] { 1, 1 });
-
- private Integer[] version;
-
- private Version(Integer[] version) {
- this.version = version;
- }
-
- public int major() { return version[0]; }
- public int minor() { return version[1]; }
-
- public String getRepresentation() {
- return version[0] + "." + version[1];
- }
+ /**
+ * Block styles use indentation to denote nesting and scope within the document. In contrast, flow
+ * styles rely on explicit indicators to denote nesting and scope.
+ *
+ * @see <a href="http://www.yaml.org/spec/current.html#id2509255">3.2.3.1. Node Styles
+ * (http://yaml.org/spec/1.1)</a>
+ */
+ public enum FlowStyle {
+ FLOW(Boolean.TRUE), BLOCK(Boolean.FALSE), AUTO(null);
- @Override
- public String toString() {
- return "Version: " + getRepresentation();
- }
- }
+ private final Boolean styleBoolean;
- private ScalarStyle defaultStyle = ScalarStyle.PLAIN;
- private FlowStyle defaultFlowStyle = FlowStyle.AUTO;
- private boolean canonical = false;
- private boolean allowUnicode = true;
- private boolean allowReadOnlyProperties = false;
- private int indent = 2;
- private int indicatorIndent = 0;
- private int bestWidth = 80;
- private boolean splitLines = true;
- private LineBreak lineBreak = LineBreak.UNIX;
- private boolean explicitStart = false;
- private boolean explicitEnd = false;
- private TimeZone timeZone = null;
-
- private Version version = null;
- private Map<String, String> tags = null;
- private Boolean prettyFlow = false;
- private AnchorGenerator anchorGenerator = new NumberAnchorGenerator(0);
-
- public boolean isAllowUnicode() {
- return allowUnicode;
+ FlowStyle(Boolean flowStyle) {
+ styleBoolean = flowStyle;
}
- /**
- * Specify whether to emit non-ASCII printable Unicode characters.
- * The default value is true.
- * When set to false then printable non-ASCII characters (Cyrillic, Chinese etc)
- * will be not printed but escaped (to support ASCII terminals)
+ /*
+ * Convenience for legacy constructors that took {@link Boolean} arguments since replaced by
+ * {@link FlowStyle}. Introduced in v1.22 but only to support that for backwards compatibility.
*
- * @param allowUnicode
- * if allowUnicode is false then all non-ASCII characters are
- * escaped
+ * @deprecated Since restored in v1.22. Use the {@link FlowStyle} constants in your code
+ * instead.
*/
- public void setAllowUnicode(boolean allowUnicode) {
- this.allowUnicode = allowUnicode;
+ @Deprecated
+ public static FlowStyle fromBoolean(Boolean flowStyle) {
+ return flowStyle == null ? AUTO : flowStyle ? FLOW : BLOCK;
}
- public ScalarStyle getDefaultScalarStyle() {
- return defaultStyle;
+ public Boolean getStyleBoolean() {
+ return styleBoolean;
}
- /**
- * Set default style for scalars. See YAML 1.1 specification, 2.3 Scalars
- * (http://yaml.org/spec/1.1/#id858081)
- *
- * @param defaultStyle
- * set the style for all scalars
- */
- public void setDefaultScalarStyle(ScalarStyle defaultStyle) {
- if (defaultStyle == null) {
- throw new NullPointerException("Use ScalarStyle enum.");
- }
- this.defaultStyle = defaultStyle;
+ @Override
+ public String toString() {
+ return "Flow style: '" + styleBoolean + "'";
}
+ }
- public void setIndent(int indent) {
- if (indent < Emitter.MIN_INDENT) {
- throw new YAMLException("Indent must be at least " + Emitter.MIN_INDENT);
- }
- if (indent > Emitter.MAX_INDENT) {
- throw new YAMLException("Indent must be at most " + Emitter.MAX_INDENT);
- }
- this.indent = indent;
- }
+ /**
+ * Platform dependent line break.
+ */
+ public enum LineBreak {
+ WIN("\r\n"), MAC("\r"), UNIX("\n");
- public int getIndent() {
- return this.indent;
- }
+ private final String lineBreak;
- public void setIndicatorIndent(int indicatorIndent) {
- if (indicatorIndent < 0) {
- throw new YAMLException("Indicator indent must be non-negative.");
- }
- if (indicatorIndent > Emitter.MAX_INDENT - 1) {
- throw new YAMLException("Indicator indent must be at most Emitter.MAX_INDENT-1: " + (Emitter.MAX_INDENT - 1));
- }
- this.indicatorIndent = indicatorIndent;
+ LineBreak(String lineBreak) {
+ this.lineBreak = lineBreak;
}
- public int getIndicatorIndent() {
- return this.indicatorIndent;
+ public String getString() {
+ return lineBreak;
}
- public void setVersion(Version version) {
- this.version = version;
+ @Override
+ public String toString() {
+ return "Line break: " + name();
}
- public Version getVersion() {
- return this.version;
- }
-
- /**
- * Force the emitter to produce a canonical YAML document.
- *
- * @param canonical
- * true produce canonical YAML document
- */
- public void setCanonical(boolean canonical) {
- this.canonical = canonical;
- }
-
- public boolean isCanonical() {
- return this.canonical;
- }
-
- /**
- * Force the emitter to produce a pretty YAML document when using the flow
- * style.
- *
- * @param prettyFlow
- * true produce pretty flow YAML document
- */
- public void setPrettyFlow(boolean prettyFlow) {
- this.prettyFlow = prettyFlow;
- }
-
- public boolean isPrettyFlow() {
- return this.prettyFlow;
- }
-
- /**
- * Specify the preferred width to emit scalars. When the scalar
- * representation takes more then the preferred with the scalar will be
- * split into a few lines. The default is 80.
- *
- * @param bestWidth
- * the preferred width for scalars.
- */
- public void setWidth(int bestWidth) {
- this.bestWidth = bestWidth;
- }
-
- public int getWidth() {
- return this.bestWidth;
- }
-
- /**
- * Specify whether to split lines exceeding preferred width for
- * scalars. The default is true.
- *
- * @param splitLines
- * whether to split lines exceeding preferred width for scalars.
- */
- public void setSplitLines(boolean splitLines) {
- this.splitLines = splitLines;
- }
-
- public boolean getSplitLines() {
- return this.splitLines;
- }
-
- public LineBreak getLineBreak() {
- return lineBreak;
- }
-
- public void setDefaultFlowStyle(FlowStyle defaultFlowStyle) {
- if (defaultFlowStyle == null) {
- throw new NullPointerException("Use FlowStyle enum.");
+ public static LineBreak getPlatformLineBreak() {
+ String platformLineBreak = System.getProperty("line.separator");
+ for (LineBreak lb : values()) {
+ if (lb.lineBreak.equals(platformLineBreak)) {
+ return lb;
}
- this.defaultFlowStyle = defaultFlowStyle;
+ }
+ return LineBreak.UNIX;
}
+ }
- public FlowStyle getDefaultFlowStyle() {
- return defaultFlowStyle;
- }
-
- /**
- * Specify the line break to separate the lines. It is platform specific:
- * Windows - "\r\n", old MacOS - "\r", Unix - "\n". The default value is the
- * one for Unix.
- */
- public void setLineBreak(LineBreak lineBreak) {
- if (lineBreak == null) {
- throw new NullPointerException("Specify line break.");
- }
- this.lineBreak = lineBreak;
- }
+ /**
+ * Specification version. Currently supported 1.0 and 1.1
+ */
+ public enum Version {
+ V1_0(new Integer[] {1, 0}), V1_1(new Integer[] {1, 1});
- public boolean isExplicitStart() {
- return explicitStart;
- }
+ private final Integer[] version;
- public void setExplicitStart(boolean explicitStart) {
- this.explicitStart = explicitStart;
+ Version(Integer[] version) {
+ this.version = version;
}
- public boolean isExplicitEnd() {
- return explicitEnd;
+ public int major() {
+ return version[0];
}
- public void setExplicitEnd(boolean explicitEnd) {
- this.explicitEnd = explicitEnd;
+ public int minor() {
+ return version[1];
}
- public Map<String, String> getTags() {
- return tags;
+ public String getRepresentation() {
+ return version[0] + "." + version[1];
}
- // TODO should use Tag ???
- public void setTags(Map<String, String> tags) {
- this.tags = tags;
+ @Override
+ public String toString() {
+ return "Version: " + getRepresentation();
}
+ }
+ public enum NonPrintableStyle {
/**
- * Report whether read-only JavaBean properties (the ones without setters)
- * should be included in the YAML document
- *
- * @return false when read-only JavaBean properties are not emitted
+ * Transform String to binary if it contains non-printable characters
*/
- public boolean isAllowReadOnlyProperties() {
- return allowReadOnlyProperties;
- }
-
+ BINARY,
/**
- * Set to true to include read-only JavaBean properties (the ones without
- * setters) in the YAML document. By default these properties are not
- * included to be able to parse later the same JavaBean.
- *
- * @param allowReadOnlyProperties
- * - true to dump read-only JavaBean properties
- */
- public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) {
- this.allowReadOnlyProperties = allowReadOnlyProperties;
- }
-
- public TimeZone getTimeZone() {
- return timeZone;
- }
-
- /**
- * Set the timezone to be used for Date. If set to <code>null</code> UTC is
- * used.
+ * Escape non-printable characters
*/
- public void setTimeZone(TimeZone timeZone) {
- this.timeZone = timeZone;
- }
-
-
- public AnchorGenerator getAnchorGenerator() {
- return anchorGenerator;
- }
-
- public void setAnchorGenerator(AnchorGenerator anchorGenerator) {
- this.anchorGenerator = anchorGenerator;
- }
+ ESCAPE
+ }
+
+ private ScalarStyle defaultStyle = ScalarStyle.PLAIN;
+ private FlowStyle defaultFlowStyle = FlowStyle.AUTO;
+ private boolean canonical = false;
+ private boolean allowUnicode = true;
+ private boolean allowReadOnlyProperties = false;
+ private int indent = 2;
+ private int indicatorIndent = 0;
+ private boolean indentWithIndicator = false;
+ private int bestWidth = 80;
+ private boolean splitLines = true;
+ private LineBreak lineBreak = LineBreak.UNIX;
+ private boolean explicitStart = false;
+ private boolean explicitEnd = false;
+ private TimeZone timeZone = null;
+ private int maxSimpleKeyLength = 128;
+ private boolean processComments = false;
+ private NonPrintableStyle nonPrintableStyle = NonPrintableStyle.BINARY;
+
+ private Version version = null;
+ private Map<String, String> tags = null;
+ private Boolean prettyFlow = false;
+ private AnchorGenerator anchorGenerator = new NumberAnchorGenerator(0);
+
+ public boolean isAllowUnicode() {
+ return allowUnicode;
+ }
+
+ /**
+ * Specify whether to emit non-ASCII printable Unicode characters. The default value is true. When
+ * set to false then printable non-ASCII characters (Cyrillic, Chinese etc) will be not printed
+ * but escaped (to support ASCII terminals)
+ *
+ * @param allowUnicode if allowUnicode is false then all non-ASCII characters are escaped
+ */
+ public void setAllowUnicode(boolean allowUnicode) {
+ this.allowUnicode = allowUnicode;
+ }
+
+ public ScalarStyle getDefaultScalarStyle() {
+ return defaultStyle;
+ }
+
+ /**
+ * Set default style for scalars. See YAML 1.1 specification, 2.3 Scalars
+ * (http://yaml.org/spec/1.1/#id858081)
+ *
+ * @param defaultStyle set the style for all scalars
+ */
+ public void setDefaultScalarStyle(ScalarStyle defaultStyle) {
+ if (defaultStyle == null) {
+ throw new NullPointerException("Use ScalarStyle enum.");
+ }
+ this.defaultStyle = defaultStyle;
+ }
+
+ public void setIndent(int indent) {
+ if (indent < Emitter.MIN_INDENT) {
+ throw new YAMLException("Indent must be at least " + Emitter.MIN_INDENT);
+ }
+ if (indent > Emitter.MAX_INDENT) {
+ throw new YAMLException("Indent must be at most " + Emitter.MAX_INDENT);
+ }
+ this.indent = indent;
+ }
+
+ public int getIndent() {
+ return this.indent;
+ }
+
+ /**
+ * Set number of white spaces to use for the sequence indicator '-'
+ *
+ * @param indicatorIndent value to be used as indent
+ */
+ public void setIndicatorIndent(int indicatorIndent) {
+ if (indicatorIndent < 0) {
+ throw new YAMLException("Indicator indent must be non-negative.");
+ }
+ if (indicatorIndent > Emitter.MAX_INDENT - 1) {
+ throw new YAMLException(
+ "Indicator indent must be at most Emitter.MAX_INDENT-1: " + (Emitter.MAX_INDENT - 1));
+ }
+ this.indicatorIndent = indicatorIndent;
+ }
+
+ public int getIndicatorIndent() {
+ return this.indicatorIndent;
+ }
+
+ public boolean getIndentWithIndicator() {
+ return indentWithIndicator;
+ }
+
+ /**
+ * Set to true to add the indent for sequences to the general indent
+ *
+ * @param indentWithIndicator - true when indent for sequences is added to general
+ */
+ public void setIndentWithIndicator(boolean indentWithIndicator) {
+ this.indentWithIndicator = indentWithIndicator;
+ }
+
+ public void setVersion(Version version) {
+ this.version = version;
+ }
+
+ public Version getVersion() {
+ return this.version;
+ }
+
+ /**
+ * Force the emitter to produce a canonical YAML document.
+ *
+ * @param canonical true produce canonical YAML document
+ */
+ public void setCanonical(boolean canonical) {
+ this.canonical = canonical;
+ }
+
+ public boolean isCanonical() {
+ return this.canonical;
+ }
+
+ /**
+ * Force the emitter to produce a pretty YAML document when using the flow style.
+ *
+ * @param prettyFlow true produce pretty flow YAML document
+ */
+ public void setPrettyFlow(boolean prettyFlow) {
+ this.prettyFlow = prettyFlow;
+ }
+
+ public boolean isPrettyFlow() {
+ return this.prettyFlow;
+ }
+
+ /**
+ * Specify the preferred width to emit scalars. When the scalar representation takes more then the
+ * preferred with the scalar will be split into a few lines. The default is 80.
+ *
+ * @param bestWidth the preferred width for scalars.
+ */
+ public void setWidth(int bestWidth) {
+ this.bestWidth = bestWidth;
+ }
+
+ public int getWidth() {
+ return this.bestWidth;
+ }
+
+ /**
+ * Specify whether to split lines exceeding preferred width for scalars. The default is true.
+ *
+ * @param splitLines whether to split lines exceeding preferred width for scalars.
+ */
+ public void setSplitLines(boolean splitLines) {
+ this.splitLines = splitLines;
+ }
+
+ public boolean getSplitLines() {
+ return this.splitLines;
+ }
+
+ public LineBreak getLineBreak() {
+ return lineBreak;
+ }
+
+ public void setDefaultFlowStyle(FlowStyle defaultFlowStyle) {
+ if (defaultFlowStyle == null) {
+ throw new NullPointerException("Use FlowStyle enum.");
+ }
+ this.defaultFlowStyle = defaultFlowStyle;
+ }
+
+ public FlowStyle getDefaultFlowStyle() {
+ return defaultFlowStyle;
+ }
+
+ /**
+ * Specify the line break to separate the lines. It is platform specific: Windows - "\r\n", old
+ * MacOS - "\r", Unix - "\n". The default value is the one for Unix.
+ *
+ * @param lineBreak to be used for the input
+ */
+ public void setLineBreak(LineBreak lineBreak) {
+ if (lineBreak == null) {
+ throw new NullPointerException("Specify line break.");
+ }
+ this.lineBreak = lineBreak;
+ }
+
+ public boolean isExplicitStart() {
+ return explicitStart;
+ }
+
+ public void setExplicitStart(boolean explicitStart) {
+ this.explicitStart = explicitStart;
+ }
+
+ public boolean isExplicitEnd() {
+ return explicitEnd;
+ }
+
+ public void setExplicitEnd(boolean explicitEnd) {
+ this.explicitEnd = explicitEnd;
+ }
+
+ public Map<String, String> getTags() {
+ return tags;
+ }
+
+ public void setTags(Map<String, String> tags) {
+ this.tags = tags;
+ }
+
+ /**
+ * Report whether read-only JavaBean properties (the ones without setters) should be included in
+ * the YAML document
+ *
+ * @return false when read-only JavaBean properties are not emitted
+ */
+ public boolean isAllowReadOnlyProperties() {
+ return allowReadOnlyProperties;
+ }
+
+ /**
+ * Set to true to include read-only JavaBean properties (the ones without setters) in the YAML
+ * document. By default these properties are not included to be able to parse later the same
+ * JavaBean.
+ *
+ * @param allowReadOnlyProperties - true to dump read-only JavaBean properties
+ */
+ public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) {
+ this.allowReadOnlyProperties = allowReadOnlyProperties;
+ }
+
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ /**
+ * Set the timezone to be used for Date. If set to <code>null</code> UTC is used.
+ *
+ * @param timeZone for created Dates or null to use UTC
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+
+ public AnchorGenerator getAnchorGenerator() {
+ return anchorGenerator;
+ }
+
+ public void setAnchorGenerator(AnchorGenerator anchorGenerator) {
+ this.anchorGenerator = anchorGenerator;
+ }
+
+ public int getMaxSimpleKeyLength() {
+ return maxSimpleKeyLength;
+ }
+
+ /**
+ * Define max key length to use simple key (without '?') More info
+ * https://yaml.org/spec/1.1/#id934537
+ *
+ * @param maxSimpleKeyLength - the limit after which the key gets explicit key indicator '?'
+ */
+ public void setMaxSimpleKeyLength(int maxSimpleKeyLength) {
+ if (maxSimpleKeyLength > 1024) {
+ throw new YAMLException(
+ "The simple key must not span more than 1024 stream characters. See https://yaml.org/spec/1.1/#id934537");
+ }
+ this.maxSimpleKeyLength = maxSimpleKeyLength;
+ }
+
+ /**
+ * Set the comment processing. By default comments are ignored.
+ *
+ * @param processComments <code>true</code> to process; <code>false</code> to ignore
+ */
+
+ public void setProcessComments(boolean processComments) {
+ this.processComments = processComments;
+ }
+
+ public boolean isProcessComments() {
+ return processComments;
+ }
+
+ public NonPrintableStyle getNonPrintableStyle() {
+ return this.nonPrintableStyle;
+ }
+
+ /**
+ * When String contains non-printable characters SnakeYAML convert it to binary data with the
+ * !!binary tag. Set this to ESCAPE to keep the !!str tag and escape the non-printable chars with
+ * \\x or \\u
+ *
+ * @param style ESCAPE to force SnakeYAML to keep !!str tag for non-printable data
+ */
+ public void setNonPrintableStyle(NonPrintableStyle style) {
+ this.nonPrintableStyle = style;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/LoaderOptions.java b/src/main/java/org/yaml/snakeyaml/LoaderOptions.java
new file mode 100644
index 00000000..78583587
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/LoaderOptions.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml;
+
+public class LoaderOptions {
+
+ private boolean allowDuplicateKeys = true;
+ private boolean wrappedToRootException = false;
+ private int maxAliasesForCollections = 50; // to prevent YAML at
+ // https://en.wikipedia.org/wiki/Billion_laughs_attack
+ private boolean allowRecursiveKeys = false;
+ private boolean processComments = false;
+ private boolean enumCaseSensitive = true;
+ private int nestingDepthLimit = 50;
+ private int codePointLimit = 3 * 1024 * 1024; // 3 MB
+
+ public final boolean isAllowDuplicateKeys() {
+ return allowDuplicateKeys;
+ }
+
+ /**
+ * Allow/Reject duplicate map keys in the YAML file.
+ *
+ * Default is to allow.
+ *
+ * YAML 1.1 is slightly vague around duplicate entries in the YAML file. The best reference is
+ * <a href="http://www.yaml.org/spec/1.1/#id862121"> 3.2.1.3. Nodes Comparison</a> where it hints
+ * that a duplicate map key is an error.
+ *
+ * For future reference, YAML spec 1.2 is clear. The keys MUST be unique.
+ * <a href="http://www.yaml.org/spec/1.2/spec.html#id2759572">1.3. Relation to JSON</a>
+ *
+ * @param allowDuplicateKeys false to reject duplicate mapping keys
+ */
+ public void setAllowDuplicateKeys(boolean allowDuplicateKeys) {
+ this.allowDuplicateKeys = allowDuplicateKeys;
+ }
+
+ public final boolean isWrappedToRootException() {
+ return wrappedToRootException;
+ }
+
+ /**
+ * Wrap runtime exception to YAMLException during parsing or leave them as they are
+ *
+ * Default is to leave original exceptions
+ *
+ * @param wrappedToRootException - true to convert runtime exception to YAMLException
+ */
+ public void setWrappedToRootException(boolean wrappedToRootException) {
+ this.wrappedToRootException = wrappedToRootException;
+ }
+
+ public final int getMaxAliasesForCollections() {
+ return maxAliasesForCollections;
+ }
+
+ /**
+ * Restrict the amount of aliases for collections (sequences and mappings) to avoid
+ * https://en.wikipedia.org/wiki/Billion_laughs_attack
+ *
+ * @param maxAliasesForCollections set max allowed value (50 by default)
+ */
+ public void setMaxAliasesForCollections(int maxAliasesForCollections) {
+ this.maxAliasesForCollections = maxAliasesForCollections;
+ }
+
+ /**
+ * Allow recursive keys for mappings. By default, it is not allowed. This setting only prevents
+ * the case when the key is the value. If the key is only a part of the value (the value is a
+ * sequence or a mapping) then this case is not recognized and always allowed.
+ *
+ * @param allowRecursiveKeys - false to disable recursive keys
+ */
+ public void setAllowRecursiveKeys(boolean allowRecursiveKeys) {
+ this.allowRecursiveKeys = allowRecursiveKeys;
+ }
+
+ public final boolean getAllowRecursiveKeys() {
+ return allowRecursiveKeys;
+ }
+
+ /**
+ * Set the comment processing. By default, comments are ignored.
+ *
+ * @param processComments <code>true</code> to process; <code>false</code> to ignore
+ */
+ public LoaderOptions setProcessComments(boolean processComments) {
+ this.processComments = processComments;
+ return this;
+ }
+
+ public final boolean isProcessComments() {
+ return processComments;
+ }
+
+ public final boolean isEnumCaseSensitive() {
+ return enumCaseSensitive;
+ }
+
+ /**
+ * Disables or enables case sensitivity during construct enum constant from string value Default
+ * is false.
+ *
+ * @param enumCaseSensitive - true to set enum case sensitive, false the reverse
+ */
+ public void setEnumCaseSensitive(boolean enumCaseSensitive) {
+ this.enumCaseSensitive = enumCaseSensitive;
+ }
+
+ public final int getNestingDepthLimit() {
+ return nestingDepthLimit;
+ }
+
+ /**
+ * Set max depth of nested collections. When the limit is exceeded an exception is thrown.
+ * Aliases/Anchors are not counted. This is to prevent a DoS attack
+ *
+ * @param nestingDepthLimit - depth to be accepted (50 by default)
+ */
+ public void setNestingDepthLimit(int nestingDepthLimit) {
+ this.nestingDepthLimit = nestingDepthLimit;
+ }
+
+ public final int getCodePointLimit() {
+ return codePointLimit;
+ }
+
+ /**
+ * The max amount of code points in the input YAML document. Please be aware that byte limit
+ * depends on the encoding.
+ *
+ * @param codePointLimit - the max allowed size of the YAML data
+ */
+ public void setCodePointLimit(int codePointLimit) {
+ this.codePointLimit = codePointLimit;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/TypeDescription.java b/src/main/java/org/yaml/snakeyaml/TypeDescription.java
index 4c383076..e65308b1 100644
--- a/src/main/java/org/yaml/snakeyaml/TypeDescription.java
+++ b/src/main/java/org/yaml/snakeyaml/TypeDescription.java
@@ -1,148 +1,398 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml;
-import java.util.HashMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.Map;
-
+import java.util.Set;
+import java.util.logging.Logger;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.introspector.BeanAccess;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.introspector.PropertySubstitute;
+import org.yaml.snakeyaml.introspector.PropertyUtils;
+import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
/**
- * Provides additional runtime information necessary to create a custom Java
- * instance.
+ * Provides additional runtime information necessary to create a custom Java instance.
+ *
+ * In general this class is thread-safe and can be used as a singleton, the only exception being the
+ * PropertyUtils field. A singleton PropertyUtils should be constructed and shared between all YAML
+ * Constructors used if a singleton TypeDescription is used, since Constructor sets its
+ * propertyUtils to the TypeDescription that is passed to it, hence you may end up in a situation
+ * when propertyUtils in TypeDescription is from different Constructor.
*/
-public final class TypeDescription {
- private final Class<? extends Object> type;
- private Tag tag;
- private Map<String, Class<? extends Object>> listProperties;
- private Map<String, Class<? extends Object>> keyProperties;
- private Map<String, Class<? extends Object>> valueProperties;
-
- public TypeDescription(Class<? extends Object> clazz, Tag tag) {
- this.type = clazz;
- this.tag = tag;
- listProperties = new HashMap<String, Class<? extends Object>>();
- keyProperties = new HashMap<String, Class<? extends Object>>();
- valueProperties = new HashMap<String, Class<? extends Object>>();
- }
+public class TypeDescription {
+
+ final static private Logger log = Logger.getLogger(TypeDescription.class.getPackage().getName());
+
+ private final Class<? extends Object> type;
+
+ // class that implements the described type; if set, will be used as a source for constructor.
+ // If not set - TypeDescription will leave instantiation of an entity to the YAML Constructor
+ private Class<?> impl;
+
+ private Tag tag;
+
+ private transient Set<Property> dumpProperties;
+ private transient PropertyUtils propertyUtils;
+ private transient boolean delegatesChecked;
+
+ private Map<String, PropertySubstitute> properties = Collections.emptyMap();
+
+ protected Set<String> excludes = Collections.emptySet();
+ protected String[] includes = null;
+ protected BeanAccess beanAccess;
+
+ public TypeDescription(Class<? extends Object> clazz, Tag tag) {
+ this(clazz, tag, null);
+ }
+
+ public TypeDescription(Class<? extends Object> clazz, Tag tag, Class<?> impl) {
+ this.type = clazz;
+ this.tag = tag;
+ this.impl = impl;
+ beanAccess = null;
+ }
+
+ public TypeDescription(Class<? extends Object> clazz, String tag) {
+ this(clazz, new Tag(tag), null);
+ }
+
+ public TypeDescription(Class<? extends Object> clazz) {
+ this(clazz, new Tag(clazz), null);
+ }
+
+ public TypeDescription(Class<? extends Object> clazz, Class<?> impl) {
+ this(clazz, new Tag(clazz), impl);
+ }
+
+ /**
+ * Get tag which shall be used to load or dump the type (class).
+ *
+ * @return tag to be used. It may be a tag for Language-Independent Types
+ * (http://www.yaml.org/type/)
+ */
+ public Tag getTag() {
+ return tag;
+ }
+
+ /**
+ * Set tag to be used dump the type (class).
+ *
+ * @param tag - local or global tag
+ * @deprecated it will be removed because it is not used
+ */
+ @Deprecated
+ public void setTag(Tag tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * Set tag to be used to dump the type (class).
+ *
+ * @param tag - local or global tag
+ * @deprecated it will be removed because it is not used
+ */
+ @Deprecated
+ public void setTag(String tag) {
+ setTag(new Tag(tag));
+ }
+
+ /**
+ * Get represented type (class)
+ *
+ * @return type (class) to be described.
+ */
+ public Class<? extends Object> getType() {
+ return type;
+ }
+
+ /**
+ * Specify that the property is a type-safe <code>List</code>.
+ *
+ * @param property name of the JavaBean property
+ * @param type class of List values
+ */
+ @Deprecated
+ public void putListPropertyType(String property, Class<? extends Object> type) {
+ addPropertyParameters(property, type);
+ }
- public TypeDescription(Class<? extends Object> clazz, String tag) {
- this(clazz, new Tag(tag));
+ /**
+ * Get class of List values for provided JavaBean property.
+ *
+ * @param property property name
+ * @return class of List values
+ */
+ @Deprecated
+ public Class<? extends Object> getListPropertyType(String property) {
+ if (properties.containsKey(property)) {
+ Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
+ if (typeArguments != null && typeArguments.length > 0) {
+ return typeArguments[0];
+ }
}
+ return null;
+ }
- public TypeDescription(Class<? extends Object> clazz) {
- this(clazz, (Tag) null);
+ /**
+ * Specify that the property is a type-safe <code>Map</code>.
+ *
+ * @param property property name of this JavaBean
+ * @param key class of keys in Map
+ * @param value class of values in Map
+ */
+ @Deprecated
+ public void putMapPropertyType(String property, Class<? extends Object> key,
+ Class<? extends Object> value) {
+ addPropertyParameters(property, key, value);
+ }
+
+ /**
+ * Get keys type info for this JavaBean
+ *
+ * @param property property name of this JavaBean
+ * @return class of keys in the Map
+ */
+ @Deprecated
+ public Class<? extends Object> getMapKeyType(String property) {
+ if (properties.containsKey(property)) {
+ Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
+ if (typeArguments != null && typeArguments.length > 0) {
+ return typeArguments[0];
+ }
}
+ return null;
+ }
- /**
- * Get tag which shall be used to load or dump the type (class).
- *
- * @return tag to be used. It may be a tag for Language-Independent Types
- * (http://www.yaml.org/type/)
- */
- public Tag getTag() {
- return tag;
+ /**
+ * Get values type info for this JavaBean
+ *
+ * @param property property name of this JavaBean
+ * @return class of values in the Map
+ */
+ @Deprecated
+ public Class<? extends Object> getMapValueType(String property) {
+ if (properties.containsKey(property)) {
+ Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
+ if (typeArguments != null && typeArguments.length > 1) {
+ return typeArguments[1];
+ }
}
+ return null;
+ }
- /**
- * Set tag to be used to load or dump the type (class).
- *
- * @param tag
- * local or global tag
- */
- public void setTag(Tag tag) {
- this.tag = tag;
+ /**
+ * Adds new substitute for property <code>pName</code> parameterized by <code>classes</code> to
+ * this <code>TypeDescription</code>. If <code>pName</code> has been added before - updates
+ * parameters with <code>classes</code>.
+ *
+ * @param pName - parameter name
+ * @param classes - parameterized by
+ */
+ public void addPropertyParameters(String pName, Class<?>... classes) {
+ if (!properties.containsKey(pName)) {
+ substituteProperty(pName, null, null, null, classes);
+ } else {
+ PropertySubstitute pr = properties.get(pName);
+ pr.setActualTypeArguments(classes);
}
- public void setTag(String tag) {
- setTag(new Tag(tag));
+ }
+
+ @Override
+ public String toString() {
+ return "TypeDescription for " + getType() + " (tag='" + getTag() + "')";
+ }
+
+ private void checkDelegates() {
+ Collection<PropertySubstitute> values = properties.values();
+ for (PropertySubstitute p : values) {
+ try {
+ p.setDelegate(discoverProperty(p.getName()));
+ } catch (YAMLException e) {
+ }
}
+ delegatesChecked = true;
+ }
- /**
- * Get represented type (class)
- *
- * @return type (class) to be described.
- */
- public Class<? extends Object> getType() {
- return type;
+ private Property discoverProperty(String name) {
+ if (propertyUtils != null) {
+ if (beanAccess == null) {
+ return propertyUtils.getProperty(type, name);
+ }
+ return propertyUtils.getProperty(type, name, beanAccess);
}
+ return null;
+ }
- /**
- * Specify that the property is a type-safe <code>List</code>.
- *
- * @param property
- * name of the JavaBean property
- * @param type
- * class of List values
- */
- public void putListPropertyType(String property, Class<? extends Object> type) {
- listProperties.put(property, type);
+ public Property getProperty(String name) {
+ if (!delegatesChecked) {
+ checkDelegates();
}
+ return properties.containsKey(name) ? properties.get(name) : discoverProperty(name);
+ }
- /**
- * Get class of List values for provided JavaBean property.
- *
- * @param property
- * property name
- * @return class of List values
- */
- public Class<? extends Object> getListPropertyType(String property) {
- return listProperties.get(property);
+ /**
+ * Adds property substitute for <code>pName</code>
+ *
+ * @param pName property name
+ * @param pType property type
+ * @param getter method name for getter
+ * @param setter method name for setter
+ * @param argParams actual types for parameterized type (List&lt;?&gt;, Map&lt;?&gt;)
+ */
+ public void substituteProperty(String pName, Class<?> pType, String getter, String setter,
+ Class<?>... argParams) {
+ substituteProperty(new PropertySubstitute(pName, pType, getter, setter, argParams));
+ }
+
+ public void substituteProperty(PropertySubstitute substitute) {
+ if (Collections.EMPTY_MAP == properties) {
+ properties = new LinkedHashMap<String, PropertySubstitute>();
}
+ substitute.setTargetType(type);
+ properties.put(substitute.getName(), substitute);
+ }
+
+ public void setPropertyUtils(PropertyUtils propertyUtils) {
+ this.propertyUtils = propertyUtils;
+ }
- /**
- * Specify that the property is a type-safe <code>Map</code>.
- *
- * @param property
- * property name of this JavaBean
- * @param key
- * class of keys in Map
- * @param value
- * class of values in Map
- */
- public void putMapPropertyType(String property, Class<? extends Object> key,
- Class<? extends Object> value) {
- keyProperties.put(property, key);
- valueProperties.put(property, value);
+ /* begin: Representer */
+ public void setIncludes(String... propNames) {
+ this.includes = (propNames != null && propNames.length > 0) ? propNames : null;
+ }
+
+ public void setExcludes(String... propNames) {
+ if (propNames != null && propNames.length > 0) {
+ excludes = new HashSet<String>();
+ Collections.addAll(excludes, propNames);
+ } else {
+ excludes = Collections.emptySet();
}
+ }
- /**
- * Get keys type info for this JavaBean
- *
- * @param property
- * property name of this JavaBean
- * @return class of keys in the Map
- */
- public Class<? extends Object> getMapKeyType(String property) {
- return keyProperties.get(property);
+ public Set<Property> getProperties() {
+ if (dumpProperties != null) {
+ return dumpProperties;
}
- /**
- * Get values type info for this JavaBean
- *
- * @param property
- * property name of this JavaBean
- * @return class of values in the Map
- */
- public Class<? extends Object> getMapValueType(String property) {
- return valueProperties.get(property);
+ if (propertyUtils != null) {
+ if (includes != null) {
+ dumpProperties = new LinkedHashSet<Property>();
+ for (String propertyName : includes) {
+ if (!excludes.contains(propertyName)) {
+ dumpProperties.add(getProperty(propertyName));
+ }
+ }
+ return dumpProperties;
+ }
+
+ final Set<Property> readableProps = (beanAccess == null) ? propertyUtils.getProperties(type)
+ : propertyUtils.getProperties(type, beanAccess);
+
+ if (properties.isEmpty()) {
+ if (excludes.isEmpty()) {
+ return dumpProperties = readableProps;
+ }
+ dumpProperties = new LinkedHashSet<Property>();
+ for (Property property : readableProps) {
+ if (!excludes.contains(property.getName())) {
+ dumpProperties.add(property);
+ }
+ }
+ return dumpProperties;
+ }
+
+ if (!delegatesChecked) {
+ checkDelegates();
+ }
+
+ dumpProperties = new LinkedHashSet<Property>();
+
+ for (Property property : properties.values()) {
+ if (!excludes.contains(property.getName()) && property.isReadable()) {
+ dumpProperties.add(property);
+ }
+ }
+
+ for (Property property : readableProps) {
+ if (!excludes.contains(property.getName())) {
+ dumpProperties.add(property);
+ }
+ }
+
+ return dumpProperties;
}
+ return null;
+ }
+
+ /* end: Representer */
+
+ /*------------ Maybe something useful to override :) ---------*/
- @Override
- public String toString() {
- return "TypeDescription for " + getType() + " (tag='" + getTag() + "')";
+ public boolean setupPropertyType(String key, Node valueNode) {
+ return false;
+ }
+
+ public boolean setProperty(Object targetBean, String propertyName, Object value)
+ throws Exception {
+ return false;
+ }
+
+ /**
+ * This method should be overridden for TypeDescription implementations that are supposed to
+ * implement instantiation logic that is different from default one as implemented in YAML
+ * constructors. Note that even if you override this method, default filling of fields with
+ * variables from parsed YAML will still occur later.
+ *
+ * @param node - node to construct the instance from
+ * @return new instance
+ */
+ public Object newInstance(Node node) {
+ if (impl != null) {
+ try {
+ java.lang.reflect.Constructor<?> c = impl.getDeclaredConstructor();
+ c.setAccessible(true);
+ return c.newInstance();
+ } catch (Exception e) {
+ log.fine(e.getLocalizedMessage());
+ impl = null;
+ }
}
+ return null;
+ }
+
+ public Object newInstance(String propertyName, Node node) {
+ return null;
+ }
+
+ /**
+ * Is invoked after entity is filled with values from deserialized YAML
+ *
+ * @param obj - deserialized entity
+ * @return postprocessed deserialized entity
+ */
+ public Object finalizeConstruction(Object obj) {
+ return obj;
+ }
+
}
diff --git a/src/main/java/org/yaml/snakeyaml/Yaml.java b/src/main/java/org/yaml/snakeyaml/Yaml.java
index 5c4559cb..16114cd9 100644
--- a/src/main/java/org/yaml/snakeyaml/Yaml.java
+++ b/src/main/java/org/yaml/snakeyaml/Yaml.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml;
@@ -24,8 +22,8 @@ import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.regex.Pattern;
-
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.constructor.BaseConstructor;
@@ -46,616 +44,675 @@ import org.yaml.snakeyaml.resolver.Resolver;
import org.yaml.snakeyaml.serializer.Serializer;
/**
- * Public YAML interface. Each Thread must have its own instance.
+ * Public YAML interface. This class is not thread-safe. Which means that all the methods of the
+ * same instance can be called only by one thread. It is better to create an instance for every YAML
+ * stream.
*/
public class Yaml {
- protected final Resolver resolver;
- private String name;
- protected BaseConstructor constructor;
- protected Representer representer;
- protected DumperOptions dumperOptions;
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- */
- public Yaml() {
- this(new Constructor(), new Representer(), new DumperOptions(), new Resolver());
- }
-
- /**
- * Create Yaml instance.
- *
- * @param dumperOptions
- * DumperOptions to configure outgoing objects
- */
- public Yaml(DumperOptions dumperOptions) {
- this(new Constructor(), new Representer(), dumperOptions);
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param representer
- * Representer to emit outgoing objects
- */
- public Yaml(Representer representer) {
- this(new Constructor(), representer);
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param constructor
- * BaseConstructor to construct incoming documents
- */
- public Yaml(BaseConstructor constructor) {
- this(constructor, new Representer());
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param constructor
- * BaseConstructor to construct incoming documents
- * @param representer
- * Representer to emit outgoing objects
- */
- public Yaml(BaseConstructor constructor, Representer representer) {
- this(constructor, representer, new DumperOptions());
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param representer
- * Representer to emit outgoing objects
- * @param dumperOptions
- * DumperOptions to configure outgoing objects
- */
- public Yaml(Representer representer, DumperOptions dumperOptions) {
- this(new Constructor(), representer, dumperOptions, new Resolver());
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param constructor
- * BaseConstructor to construct incoming documents
- * @param representer
- * Representer to emit outgoing objects
- * @param dumperOptions
- * DumperOptions to configure outgoing objects
- */
- public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions) {
- this(constructor, representer, dumperOptions, new Resolver());
- }
-
- /**
- * Create Yaml instance. It is safe to create a few instances and use them
- * in different Threads.
- *
- * @param constructor
- * BaseConstructor to construct incoming documents
- * @param representer
- * Representer to emit outgoing objects
- * @param dumperOptions
- * DumperOptions to configure outgoing objects
- * @param resolver
- * Resolver to detect implicit type
- */
- public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
- Resolver resolver) {
- if (!constructor.isExplicitPropertyUtils()) {
- constructor.setPropertyUtils(representer.getPropertyUtils());
- } else if (!representer.isExplicitPropertyUtils()) {
- representer.setPropertyUtils(constructor.getPropertyUtils());
- }
- this.constructor = constructor;
- representer.setDefaultFlowStyle(dumperOptions.getDefaultFlowStyle());
- representer.setDefaultScalarStyle(dumperOptions.getDefaultScalarStyle());
- representer.getPropertyUtils().setAllowReadOnlyProperties(
- dumperOptions.isAllowReadOnlyProperties());
- representer.setTimeZone(dumperOptions.getTimeZone());
- this.representer = representer;
- this.dumperOptions = dumperOptions;
- this.resolver = resolver;
- this.name = "Yaml:" + System.identityHashCode(this);
- }
-
- /**
- * Serialize a Java object into a YAML String.
- *
- * @param data
- * Java object to be Serialized to YAML
- * @return YAML String
- */
- public String dump(Object data) {
- List<Object> list = new ArrayList<Object>(1);
- list.add(data);
- return dumpAll(list.iterator());
- }
-
- /**
- * Produce the corresponding representation tree for a given Object.
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
- * Overview</a>
- * @param data
- * instance to build the representation tree for
- * @return representation tree
- */
- public Node represent(Object data) {
- return representer.represent(data);
- }
-
- /**
- * Serialize a sequence of Java objects into a YAML String.
- *
- * @param data
- * Iterator with Objects
- * @return YAML String with all the objects in proper sequence
- */
- public String dumpAll(Iterator<? extends Object> data) {
- StringWriter buffer = new StringWriter();
- dumpAll(data, buffer, null);
- return buffer.toString();
- }
-
- /**
- * Serialize a Java object into a YAML stream.
- *
- * @param data
- * Java object to be serialized to YAML
- * @param output
- * stream to write to
- */
- public void dump(Object data, Writer output) {
- List<Object> list = new ArrayList<Object>(1);
- list.add(data);
- dumpAll(list.iterator(), output, null);
- }
-
- /**
- * Serialize a sequence of Java objects into a YAML stream.
- *
- * @param data
- * Iterator with Objects
- * @param output
- * stream to write to
- */
- public void dumpAll(Iterator<? extends Object> data, Writer output) {
- dumpAll(data, output, null);
- }
-
- private void dumpAll(Iterator<? extends Object> data, Writer output, Tag rootTag) {
- Serializer serializer = new Serializer(new Emitter(output, dumperOptions), resolver,
- dumperOptions, rootTag);
- try {
- serializer.open();
- while (data.hasNext()) {
- Node node = representer.represent(data.next());
- serializer.serialize(node);
- }
- serializer.close();
- } catch (IOException e) {
- throw new YAMLException(e);
- }
- }
- /**
- * <p>
- * Serialize a Java object into a YAML string. Override the default root tag
- * with <code>rootTag</code>.
- * </p>
- *
- * <p>
- * This method is similar to <code>Yaml.dump(data)</code> except that the
- * root tag for the whole document is replaced with the given tag. This has
- * two main uses.
- * </p>
- *
- * <p>
- * First, if the root tag is replaced with a standard YAML tag, such as
- * <code>Tag.MAP</code>, then the object will be dumped as a map. The root
- * tag will appear as <code>!!map</code>, or blank (implicit !!map).
- * </p>
- *
- * <p>
- * Second, if the root tag is replaced by a different custom tag, then the
- * document appears to be a different type when loaded. For example, if an
- * instance of MyClass is dumped with the tag !!YourClass, then it will be
- * handled as an instance of YourClass when loaded.
- * </p>
- *
- * @param data
- * Java object to be serialized to YAML
- * @param rootTag
- * the tag for the whole YAML document. The tag should be Tag.MAP
- * for a JavaBean to make the tag disappear (to use implicit tag
- * !!map). If <code>null</code> is provided then the standard tag
- * with the full class name is used.
- * @param flowStyle
- * flow style for the whole document. See Chapter 10. Collection
- * Styles http://yaml.org/spec/1.1/#id930798. If
- * <code>null</code> is provided then the flow style from
- * DumperOptions is used.
- *
- * @return YAML String
- */
- public String dumpAs(Object data, Tag rootTag, FlowStyle flowStyle) {
- FlowStyle oldStyle = representer.getDefaultFlowStyle();
- if (flowStyle != null) {
- representer.setDefaultFlowStyle(flowStyle);
- }
- List<Object> list = new ArrayList<Object>(1);
- list.add(data);
- StringWriter buffer = new StringWriter();
- dumpAll(list.iterator(), buffer, rootTag);
- representer.setDefaultFlowStyle(oldStyle);
- return buffer.toString();
- }
-
- /**
- * <p>
- * Serialize a Java object into a YAML string. Override the default root tag
- * with <code>Tag.MAP</code>.
- * </p>
- * <p>
- * This method is similar to <code>Yaml.dump(data)</code> except that the
- * root tag for the whole document is replaced with <code>Tag.MAP</code> tag
- * (implicit !!map).
- * </p>
- * <p>
- * Block Mapping is used as the collection style. See 10.2.2. Block Mappings
- * (http://yaml.org/spec/1.1/#id934537)
- * </p>
- *
- * @param data
- * Java object to be serialized to YAML
- * @return YAML String
- */
- public String dumpAsMap(Object data) {
- return dumpAs(data, Tag.MAP, FlowStyle.BLOCK);
+ protected final Resolver resolver;
+ private String name;
+ protected BaseConstructor constructor;
+ protected Representer representer;
+ protected DumperOptions dumperOptions;
+ protected LoaderOptions loadingConfig;
+
+ /**
+ * Create Yaml instance.
+ */
+ public Yaml() {
+ this(new Constructor(), new Representer(), new DumperOptions(), new LoaderOptions(),
+ new Resolver());
+ }
+
+ /**
+ * Create Yaml instance.
+ *
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ */
+ public Yaml(DumperOptions dumperOptions) {
+ this(new Constructor(), new Representer(dumperOptions), dumperOptions);
+ }
+
+ /**
+ * Create Yaml instance.
+ *
+ * @param loadingConfig LoadingConfig to control load behavior
+ */
+ public Yaml(LoaderOptions loadingConfig) {
+ this(new Constructor(loadingConfig), new Representer(), new DumperOptions(), loadingConfig);
+ }
+
+ /**
+ * Create Yaml instance.
+ *
+ * @param representer Representer to emit outgoing objects
+ */
+ public Yaml(Representer representer) {
+ this(new Constructor(), representer);
+ }
+
+ /**
+ * Create Yaml instance.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ */
+ public Yaml(BaseConstructor constructor) {
+ this(constructor, new Representer());
+ }
+
+ /**
+ * Create Yaml instance.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ * @param representer Representer to emit outgoing objects
+ */
+ public Yaml(BaseConstructor constructor, Representer representer) {
+ this(constructor, representer, initDumperOptions(representer));
+ }
+
+ private static DumperOptions initDumperOptions(Representer representer) {
+ DumperOptions dumperOptions = new DumperOptions();
+ dumperOptions.setDefaultFlowStyle(representer.getDefaultFlowStyle());
+ dumperOptions.setDefaultScalarStyle(representer.getDefaultScalarStyle());
+ dumperOptions
+ .setAllowReadOnlyProperties(representer.getPropertyUtils().isAllowReadOnlyProperties());
+ dumperOptions.setTimeZone(representer.getTimeZone());
+ return dumperOptions;
+ }
+
+ /**
+ * Create Yaml instance. It is safe to create a few instances and use them in different Threads.
+ *
+ * @param representer Representer to emit outgoing objects
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ */
+ public Yaml(Representer representer, DumperOptions dumperOptions) {
+ this(new Constructor(), representer, dumperOptions, new LoaderOptions(), new Resolver());
+ }
+
+ /**
+ * Create Yaml instance. It is safe to create a few instances and use them in different Threads.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ * @param representer Representer to emit outgoing objects
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ */
+ public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions) {
+ this(constructor, representer, dumperOptions, new LoaderOptions(), new Resolver());
+ }
+
+ /**
+ * Create Yaml instance. It is safe to create a few instances and use them in different Threads.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ * @param representer Representer to emit outgoing objects
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ * @param loadingConfig LoadingConfig to control load behavior
+ */
+ public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
+ LoaderOptions loadingConfig) {
+ this(constructor, representer, dumperOptions, loadingConfig, new Resolver());
+ }
+
+ /**
+ * Create Yaml instance. It is safe to create a few instances and use them in different Threads.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ * @param representer Representer to emit outgoing objects
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ * @param resolver Resolver to detect implicit type
+ */
+ public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
+ Resolver resolver) {
+ this(constructor, representer, dumperOptions, new LoaderOptions(), resolver);
+ }
+
+ /**
+ * Create Yaml instance. It is safe to create a few instances and use them in different Threads.
+ *
+ * @param constructor BaseConstructor to construct incoming documents
+ * @param representer Representer to emit outgoing objects
+ * @param dumperOptions DumperOptions to configure outgoing objects
+ * @param loadingConfig LoadingConfig to control load behavior
+ * @param resolver Resolver to detect implicit type
+ */
+ public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
+ LoaderOptions loadingConfig, Resolver resolver) {
+ if (!constructor.isExplicitPropertyUtils()) {
+ constructor.setPropertyUtils(representer.getPropertyUtils());
+ } else if (!representer.isExplicitPropertyUtils()) {
+ representer.setPropertyUtils(constructor.getPropertyUtils());
+ }
+ this.constructor = constructor;
+ this.constructor.setAllowDuplicateKeys(loadingConfig.isAllowDuplicateKeys());
+ this.constructor.setWrappedToRootException(loadingConfig.isWrappedToRootException());
+ if (!dumperOptions.getIndentWithIndicator()
+ && dumperOptions.getIndent() <= dumperOptions.getIndicatorIndent()) {
+ throw new YAMLException("Indicator indent must be smaller then indent.");
+ }
+ representer.setDefaultFlowStyle(dumperOptions.getDefaultFlowStyle());
+ representer.setDefaultScalarStyle(dumperOptions.getDefaultScalarStyle());
+ representer.getPropertyUtils()
+ .setAllowReadOnlyProperties(dumperOptions.isAllowReadOnlyProperties());
+ representer.setTimeZone(dumperOptions.getTimeZone());
+ this.representer = representer;
+ this.dumperOptions = dumperOptions;
+ this.loadingConfig = loadingConfig;
+ this.resolver = resolver;
+ this.name = "Yaml:" + System.identityHashCode(this);
+ }
+
+ /**
+ * Serialize a Java object into a YAML String.
+ *
+ * @param data Java object to be Serialized to YAML
+ * @return YAML String
+ */
+ public String dump(Object data) {
+ List<Object> list = new ArrayList<Object>(1);
+ list.add(data);
+ return dumpAll(list.iterator());
+ }
+
+ /**
+ * Produce the corresponding representation tree for a given Object.
+ *
+ * @param data instance to build the representation tree for
+ * @return representation tree
+ * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing Overview</a>
+ */
+ public Node represent(Object data) {
+ return representer.represent(data);
+ }
+
+ /**
+ * Serialize a sequence of Java objects into a YAML String.
+ *
+ * @param data Iterator with Objects
+ * @return YAML String with all the objects in proper sequence
+ */
+ public String dumpAll(Iterator<? extends Object> data) {
+ StringWriter buffer = new StringWriter();
+ dumpAll(data, buffer, null);
+ return buffer.toString();
+ }
+
+ /**
+ * Serialize a Java object into a YAML stream.
+ *
+ * @param data Java object to be serialized to YAML
+ * @param output stream to write to
+ */
+ public void dump(Object data, Writer output) {
+ List<Object> list = new ArrayList<Object>(1);
+ list.add(data);
+ dumpAll(list.iterator(), output, null);
+ }
+
+ /**
+ * Serialize a sequence of Java objects into a YAML stream.
+ *
+ * @param data Iterator with Objects
+ * @param output stream to write to
+ */
+ public void dumpAll(Iterator<? extends Object> data, Writer output) {
+ dumpAll(data, output, null);
+ }
+
+ private void dumpAll(Iterator<? extends Object> data, Writer output, Tag rootTag) {
+ Serializer serializer =
+ new Serializer(new Emitter(output, dumperOptions), resolver, dumperOptions, rootTag);
+ try {
+ serializer.open();
+ while (data.hasNext()) {
+ Node node = representer.represent(data.next());
+ serializer.serialize(node);
+ }
+ serializer.close();
+ } catch (IOException e) {
+ throw new YAMLException(e);
+ }
+ }
+
+ /**
+ * <p>
+ * Serialize a Java object into a YAML string. Override the default root tag with
+ * <code>rootTag</code>.
+ * </p>
+ *
+ * <p>
+ * This method is similar to <code>Yaml.dump(data)</code> except that the root tag for the whole
+ * document is replaced with the given tag. This has two main uses.
+ * </p>
+ *
+ * <p>
+ * First, if the root tag is replaced with a standard YAML tag, such as <code>Tag.MAP</code>, then
+ * the object will be dumped as a map. The root tag will appear as <code>!!map</code>, or blank
+ * (implicit !!map).
+ * </p>
+ *
+ * <p>
+ * Second, if the root tag is replaced by a different custom tag, then the document appears to be
+ * a different type when loaded. For example, if an instance of MyClass is dumped with the tag
+ * !!YourClass, then it will be handled as an instance of YourClass when loaded.
+ * </p>
+ *
+ * @param data Java object to be serialized to YAML
+ * @param rootTag the tag for the whole YAML document. The tag should be Tag.MAP for a JavaBean to
+ * make the tag disappear (to use implicit tag !!map). If <code>null</code> is provided
+ * then the standard tag with the full class name is used.
+ * @param flowStyle flow style for the whole document. See Chapter 10. Collection Styles
+ * http://yaml.org/spec/1.1/#id930798. If <code>null</code> is provided then the flow style
+ * from DumperOptions is used.
+ * @return YAML String
+ */
+ public String dumpAs(Object data, Tag rootTag, FlowStyle flowStyle) {
+ FlowStyle oldStyle = representer.getDefaultFlowStyle();
+ if (flowStyle != null) {
+ representer.setDefaultFlowStyle(flowStyle);
+ }
+ List<Object> list = new ArrayList<Object>(1);
+ list.add(data);
+ StringWriter buffer = new StringWriter();
+ dumpAll(list.iterator(), buffer, rootTag);
+ representer.setDefaultFlowStyle(oldStyle);
+ return buffer.toString();
+ }
+
+ /**
+ * <p>
+ * Serialize a Java object into a YAML string. Override the default root tag with
+ * <code>Tag.MAP</code>.
+ * </p>
+ * <p>
+ * This method is similar to <code>Yaml.dump(data)</code> except that the root tag for the whole
+ * document is replaced with <code>Tag.MAP</code> tag (implicit !!map).
+ * </p>
+ * <p>
+ * Block Mapping is used as the collection style. See 10.2.2. Block Mappings
+ * (http://yaml.org/spec/1.1/#id934537)
+ * </p>
+ *
+ * @param data Java object to be serialized to YAML
+ * @return YAML String
+ */
+ public String dumpAsMap(Object data) {
+ return dumpAs(data, Tag.MAP, FlowStyle.BLOCK);
+ }
+
+ /**
+ * Serialize (dump) a YAML node into a YAML stream.
+ *
+ * @param node YAML node to be serialized to YAML
+ * @param output stream to write to
+ */
+ public void serialize(Node node, Writer output) {
+ Serializer serializer =
+ new Serializer(new Emitter(output, dumperOptions), resolver, dumperOptions, null);
+ try {
+ serializer.open();
+ serializer.serialize(node);
+ serializer.close();
+ } catch (IOException e) {
+ throw new YAMLException(e);
+ }
+ }
+
+ /**
+ * Serialize the representation tree into Events.
+ *
+ * @param data representation tree
+ * @return Event list
+ * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
+ */
+ public List<Event> serialize(Node data) {
+ SilentEmitter emitter = new SilentEmitter();
+ Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null);
+ try {
+ serializer.open();
+ serializer.serialize(data);
+ serializer.close();
+ } catch (IOException e) {
+ throw new YAMLException(e);
+ }
+ return emitter.getEvents();
+ }
+
+ private static class SilentEmitter implements Emitable {
+
+ private final List<Event> events = new ArrayList<Event>(100);
+
+ public List<Event> getEvents() {
+ return events;
}
- /**
- * Serialize the representation tree into Events.
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
- * @param data
- * representation tree
- * @return Event list
- */
- public List<Event> serialize(Node data) {
- SilentEmitter emitter = new SilentEmitter();
- Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null);
- try {
- serializer.open();
- serializer.serialize(data);
- serializer.close();
- } catch (IOException e) {
- throw new YAMLException(e);
- }
- return emitter.getEvents();
+ @Override
+ public void emit(Event event) throws IOException {
+ events.add(event);
+ }
+ }
+
+ /**
+ * Parse the only YAML document in a String and produce the corresponding Java object. (Because
+ * the encoding in known BOM is not respected.)
+ *
+ * @param yaml YAML data to load from (BOM must not be present)
+ * @param <T> the class of the instance to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T load(String yaml) {
+ return (T) loadFromReader(new StreamReader(yaml), Object.class);
+ }
+
+ /**
+ * Parse the only YAML document in a stream and produce the corresponding Java object.
+ *
+ * @param io data to load from (BOM is respected to detect encoding and removed from the data)
+ * @param <T> the class of the instance to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T load(InputStream io) {
+ return (T) loadFromReader(new StreamReader(new UnicodeReader(io)), Object.class);
+ }
+
+ /**
+ * Parse the only YAML document in a stream and produce the corresponding Java object.
+ *
+ * @param io data to load from (BOM must not be present)
+ * @param <T> the class of the instance to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T load(Reader io) {
+ return (T) loadFromReader(new StreamReader(io), Object.class);
+ }
+
+ /**
+ * Parse the only YAML document in a stream and produce the corresponding Java object.
+ *
+ * @param <T> Class is defined by the second argument
+ * @param io data to load from (BOM must not be present)
+ * @param type Class of the object to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T loadAs(Reader io, Class<T> type) {
+ return (T) loadFromReader(new StreamReader(io), type);
+ }
+
+ /**
+ * Parse the only YAML document in a String and produce the corresponding Java object. (Because
+ * the encoding in known BOM is not respected.)
+ *
+ * @param <T> Class is defined by the second argument
+ * @param yaml YAML data to load from (BOM must not be present)
+ * @param type Class of the object to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T loadAs(String yaml, Class<T> type) {
+ return (T) loadFromReader(new StreamReader(yaml), type);
+ }
+
+ /**
+ * Parse the only YAML document in a stream and produce the corresponding Java object.
+ *
+ * @param <T> Class is defined by the second argument
+ * @param input data to load from (BOM is respected to detect encoding and removed from the data)
+ * @param type Class of the object to be created
+ * @return parsed object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T loadAs(InputStream input, Class<T> type) {
+ return (T) loadFromReader(new StreamReader(new UnicodeReader(input)), type);
+ }
+
+ private Object loadFromReader(StreamReader sreader, Class<?> type) {
+ Composer composer =
+ new Composer(new ParserImpl(sreader, loadingConfig), resolver, loadingConfig);
+ constructor.setComposer(composer);
+ return constructor.getSingleData(type);
+ }
+
+ /**
+ * Parse all YAML documents in the Reader and produce corresponding Java objects. The documents
+ * are parsed only when the iterator is invoked.
+ *
+ * @param yaml YAML data to load from (BOM must not be present)
+ * @return an Iterable over the parsed Java objects in this String in proper sequence
+ */
+ public Iterable<Object> loadAll(Reader yaml) {
+ Composer composer =
+ new Composer(new ParserImpl(new StreamReader(yaml), loadingConfig.isProcessComments()),
+ resolver, loadingConfig);
+ constructor.setComposer(composer);
+ Iterator<Object> result = new Iterator<Object>() {
+ @Override
+ public boolean hasNext() {
+ return constructor.checkData();
+ }
+
+ @Override
+ public Object next() {
+ return constructor.getData();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ return new YamlIterable(result);
+ }
+
+ private static class YamlIterable implements Iterable<Object> {
+
+ private final Iterator<Object> iterator;
+
+ public YamlIterable(Iterator<Object> iterator) {
+ this.iterator = iterator;
}
- private static class SilentEmitter implements Emitable {
- private List<Event> events = new ArrayList<Event>(100);
-
- public List<Event> getEvents() {
- return events;
- }
-
- public void emit(Event event) throws IOException {
- events.add(event);
+ @Override
+ public Iterator<Object> iterator() {
+ return iterator;
+ }
+ }
+
+ /**
+ * Parse all YAML documents in a String and produce corresponding Java objects. (Because the
+ * encoding in known BOM is not respected.) The documents are parsed only when the iterator is
+ * invoked.
+ *
+ * @param yaml YAML data to load from (BOM must not be present)
+ * @return an Iterable over the parsed Java objects in this String in proper sequence
+ */
+ public Iterable<Object> loadAll(String yaml) {
+ return loadAll(new StringReader(yaml));
+ }
+
+ /**
+ * Parse all YAML documents in a stream and produce corresponding Java objects. The documents are
+ * parsed only when the iterator is invoked.
+ *
+ * @param yaml YAML data to load from (BOM is respected to detect encoding and removed from the
+ * data)
+ * @return an Iterable over the parsed Java objects in this stream in proper sequence
+ */
+ public Iterable<Object> loadAll(InputStream yaml) {
+ return loadAll(new UnicodeReader(yaml));
+ }
+
+ /**
+ * Parse the first YAML document in a stream and produce the corresponding representation tree.
+ * (This is the opposite of the represent() method)
+ *
+ * @param yaml YAML document
+ * @return parsed root Node for the specified YAML document
+ * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing Overview</a>
+ */
+ public Node compose(Reader yaml) {
+ Composer composer =
+ new Composer(new ParserImpl(new StreamReader(yaml), loadingConfig.isProcessComments()),
+ resolver, loadingConfig);
+ return composer.getSingleNode();
+ }
+
+ /**
+ * Parse all YAML documents in a stream and produce corresponding representation trees.
+ *
+ * @param yaml stream of YAML documents
+ * @return parsed root Nodes for all the specified YAML documents
+ * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
+ */
+ public Iterable<Node> composeAll(Reader yaml) {
+ final Composer composer =
+ new Composer(new ParserImpl(new StreamReader(yaml), loadingConfig.isProcessComments()),
+ resolver, loadingConfig);
+ Iterator<Node> result = new Iterator<Node>() {
+ @Override
+ public boolean hasNext() {
+ return composer.checkNode();
+ }
+
+ @Override
+ public Node next() {
+ Node node = composer.getNode();
+ if (node != null) {
+ return node;
+ } else {
+ throw new NoSuchElementException("No Node is available.");
}
- }
+ }
- /**
- * Parse the only YAML document in a String and produce the corresponding
- * Java object. (Because the encoding in known BOM is not respected.)
- *
- * @param yaml
- * YAML data to load from (BOM must not be present)
- * @return parsed object
- */
- public Object load(String yaml) {
- return loadFromReader(new StreamReader(yaml), Object.class);
- }
-
- /**
- * Parse the only YAML document in a stream and produce the corresponding
- * Java object.
- *
- * @param io
- * data to load from (BOM is respected and removed)
- * @return parsed object
- */
- public Object load(InputStream io) {
- return loadFromReader(new StreamReader(new UnicodeReader(io)), Object.class);
- }
-
- /**
- * Parse the only YAML document in a stream and produce the corresponding
- * Java object.
- *
- * @param io
- * data to load from (BOM must not be present)
- * @return parsed object
- */
- public Object load(Reader io) {
- return loadFromReader(new StreamReader(io), Object.class);
- }
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ return new NodeIterable(result);
+ }
- /**
- * Parse the only YAML document in a stream and produce the corresponding
- * Java object.
- *
- * @param <T>
- * Class is defined by the second argument
- * @param io
- * data to load from (BOM must not be present)
- * @param type
- * Class of the object to be created
- * @return parsed object
- */
- @SuppressWarnings("unchecked")
- public <T> T loadAs(Reader io, Class<T> type) {
- return (T) loadFromReader(new StreamReader(io), type);
- }
-
- /**
- * Parse the only YAML document in a String and produce the corresponding
- * Java object. (Because the encoding in known BOM is not respected.)
- *
- * @param <T>
- * Class is defined by the second argument
- * @param yaml
- * YAML data to load from (BOM must not be present)
- * @param type
- * Class of the object to be created
- * @return parsed object
- */
- @SuppressWarnings("unchecked")
- public <T> T loadAs(String yaml, Class<T> type) {
- return (T) loadFromReader(new StreamReader(yaml), type);
- }
-
- /**
- * Parse the only YAML document in a stream and produce the corresponding
- * Java object.
- *
- * @param <T>
- * Class is defined by the second argument
- * @param input
- * data to load from (BOM is respected and removed)
- * @param type
- * Class of the object to be created
- * @return parsed object
- */
- @SuppressWarnings("unchecked")
- public <T> T loadAs(InputStream input, Class<T> type) {
- return (T) loadFromReader(new StreamReader(new UnicodeReader(input)), type);
- }
+ private static class NodeIterable implements Iterable<Node> {
- private Object loadFromReader(StreamReader sreader, Class<?> type) {
- Composer composer = new Composer(new ParserImpl(sreader), resolver);
- constructor.setComposer(composer);
- return constructor.getSingleData(type);
- }
+ private final Iterator<Node> iterator;
- /**
- * Parse all YAML documents in a String and produce corresponding Java
- * objects. The documents are parsed only when the iterator is invoked.
- *
- * @param yaml
- * YAML data to load from (BOM must not be present)
- * @return an iterator over the parsed Java objects in this String in proper
- * sequence
- */
- public Iterable<Object> loadAll(Reader yaml) {
- Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
- constructor.setComposer(composer);
- Iterator<Object> result = new Iterator<Object>() {
- public boolean hasNext() {
- return constructor.checkData();
- }
-
- public Object next() {
- return constructor.getData();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- return new YamlIterable(result);
+ public NodeIterable(Iterator<Node> iterator) {
+ this.iterator = iterator;
}
- private static class YamlIterable implements Iterable<Object> {
- private Iterator<Object> iterator;
-
- public YamlIterable(Iterator<Object> iterator) {
- this.iterator = iterator;
- }
-
- public Iterator<Object> iterator() {
- return iterator;
+ @Override
+ public Iterator<Node> iterator() {
+ return iterator;
+ }
+ }
+
+ /**
+ * Add an implicit scalar detector. If an implicit scalar value matches the given regexp, the
+ * corresponding tag is assigned to the scalar.
+ *
+ * @param tag tag to assign to the node
+ * @param regexp regular expression to match against
+ * @param first a sequence of possible initial characters or null (which means any).
+ */
+ public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
+ resolver.addImplicitResolver(tag, regexp, first);
+ }
+
+ /**
+ * Add an implicit scalar detector. If an implicit scalar value matches the given regexp, the
+ * corresponding tag is assigned to the scalar.
+ *
+ * @param tag tag to assign to the node
+ * @param regexp regular expression to match against
+ * @param first a sequence of possible initial characters or null (which means any).
+ * @param limit the max length of the value which may match the regular expression
+ */
+ public void addImplicitResolver(Tag tag, Pattern regexp, String first, int limit) {
+ resolver.addImplicitResolver(tag, regexp, first, limit);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Get a meaningful name. It simplifies debugging in a multi-threaded environment. If nothing is
+ * set explicitly the address of the instance is returned.
+ *
+ * @return human readable name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set a meaningful name to be shown in toString()
+ *
+ * @param name human readable name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Parse a YAML stream and produce parsing events.
+ *
+ * @param yaml YAML document(s)
+ * @return parsed events
+ * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
+ */
+ public Iterable<Event> parse(Reader yaml) {
+ final Parser parser = new ParserImpl(new StreamReader(yaml), loadingConfig.isProcessComments());
+ Iterator<Event> result = new Iterator<Event>() {
+ @Override
+ public boolean hasNext() {
+ return parser.peekEvent() != null;
+ }
+
+ @Override
+ public Event next() {
+ Event event = parser.getEvent();
+ if (event != null) {
+ return event;
+ } else {
+ throw new NoSuchElementException("No Event is available.");
}
- }
-
- /**
- * Parse all YAML documents in a String and produce corresponding Java
- * objects. (Because the encoding in known BOM is not respected.) The
- * documents are parsed only when the iterator is invoked.
- *
- * @param yaml
- * YAML data to load from (BOM must not be present)
- * @return an iterator over the parsed Java objects in this String in proper
- * sequence
- */
- public Iterable<Object> loadAll(String yaml) {
- return loadAll(new StringReader(yaml));
- }
-
- /**
- * Parse all YAML documents in a stream and produce corresponding Java
- * objects. The documents are parsed only when the iterator is invoked.
- *
- * @param yaml
- * YAML data to load from (BOM is respected and ignored)
- * @return an iterator over the parsed Java objects in this stream in proper
- * sequence
- */
- public Iterable<Object> loadAll(InputStream yaml) {
- return loadAll(new UnicodeReader(yaml));
- }
+ }
- /**
- * Parse the first YAML document in a stream and produce the corresponding
- * representation tree. (This is the opposite of the represent() method)
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
- * Overview</a>
- * @param yaml
- * YAML document
- * @return parsed root Node for the specified YAML document
- */
- public Node compose(Reader yaml) {
- Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
- constructor.setComposer(composer);
- return composer.getSingleNode();
- }
-
- /**
- * Parse all YAML documents in a stream and produce corresponding
- * representation trees.
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
- * @param yaml
- * stream of YAML documents
- * @return parsed root Nodes for all the specified YAML documents
- */
- public Iterable<Node> composeAll(Reader yaml) {
- final Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
- constructor.setComposer(composer);
- Iterator<Node> result = new Iterator<Node>() {
- public boolean hasNext() {
- return composer.checkNode();
- }
-
- public Node next() {
- return composer.getNode();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- return new NodeIterable(result);
- }
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ return new EventIterable(result);
+ }
- private static class NodeIterable implements Iterable<Node> {
- private Iterator<Node> iterator;
-
- public NodeIterable(Iterator<Node> iterator) {
- this.iterator = iterator;
- }
+ private static class EventIterable implements Iterable<Event> {
- public Iterator<Node> iterator() {
- return iterator;
- }
- }
+ private final Iterator<Event> iterator;
- /**
- * Add an implicit scalar detector. If an implicit scalar value matches the
- * given regexp, the corresponding tag is assigned to the scalar.
- *
- * @param tag
- * tag to assign to the node
- * @param regexp
- * regular expression to match against
- * @param first
- * a sequence of possible initial characters or null (which means
- * any).
- */
- public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
- resolver.addImplicitResolver(tag, regexp, first);
+ public EventIterable(Iterator<Event> iterator) {
+ this.iterator = iterator;
}
@Override
- public String toString() {
- return name;
- }
-
- /**
- * Get a meaningful name. It simplifies debugging in a multi-threaded
- * environment. If nothing is set explicitly the address of the instance is
- * returned.
- *
- * @return human readable name
- */
- public String getName() {
- return name;
- }
-
- /**
- * Set a meaningful name to be shown in toString()
- *
- * @param name
- * human readable name
- */
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * Parse a YAML stream and produce parsing events.
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
- * @param yaml
- * YAML document(s)
- * @return parsed events
- */
- public Iterable<Event> parse(Reader yaml) {
- final Parser parser = new ParserImpl(new StreamReader(yaml));
- Iterator<Event> result = new Iterator<Event>() {
- public boolean hasNext() {
- return parser.peekEvent() != null;
- }
-
- public Event next() {
- return parser.getEvent();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- return new EventIterable(result);
- }
-
- private static class EventIterable implements Iterable<Event> {
- private Iterator<Event> iterator;
-
- public EventIterable(Iterator<Event> iterator) {
- this.iterator = iterator;
- }
-
- public Iterator<Event> iterator() {
- return iterator;
- }
+ public Iterator<Event> iterator() {
+ return iterator;
}
+ }
- public void setBeanAccess(BeanAccess beanAccess) {
- constructor.getPropertyUtils().setBeanAccess(beanAccess);
- representer.getPropertyUtils().setBeanAccess(beanAccess);
- }
+ public void setBeanAccess(BeanAccess beanAccess) {
+ constructor.getPropertyUtils().setBeanAccess(beanAccess);
+ representer.getPropertyUtils().setBeanAccess(beanAccess);
+ }
+ public void addTypeDescription(TypeDescription td) {
+ constructor.addTypeDescription(td);
+ representer.addTypeDescription(td);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java b/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java
new file mode 100644
index 00000000..f1994217
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java
@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.comments;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import org.yaml.snakeyaml.events.CommentEvent;
+import org.yaml.snakeyaml.events.Event;
+import org.yaml.snakeyaml.parser.Parser;
+
+/**
+ * Used by the Composer and Emitter to collect comment events so that they can be used at a later
+ * point in the process.
+ */
+public class CommentEventsCollector {
+
+ private List<CommentLine> commentLineList;
+ private final Queue<Event> eventSource;
+ private final CommentType[] expectedCommentTypes;
+
+ /**
+ * Constructor used to collect comment events emitted by a Parser.
+ *
+ * @param parser the event source.
+ * @param expectedCommentTypes the comment types expected. Any comment types not included are not
+ * collected.
+ */
+ public CommentEventsCollector(final Parser parser, CommentType... expectedCommentTypes) {
+ this.eventSource = new AbstractQueue<Event>() {
+
+ @Override
+ public boolean offer(Event e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Event poll() {
+ return parser.getEvent();
+ }
+
+ @Override
+ public Event peek() {
+ return parser.peekEvent();
+ }
+
+ @Override
+ public Iterator<Event> iterator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ this.expectedCommentTypes = expectedCommentTypes;
+ commentLineList = new ArrayList<>();
+ }
+
+ /**
+ * Constructor used to collect events emitted by the Serializer.
+ *
+ * @param eventSource the event source.
+ *
+ * @param expectedCommentTypes the comment types expected. Any comment types not included are not
+ * collected.
+ */
+ public CommentEventsCollector(Queue<Event> eventSource, CommentType... expectedCommentTypes) {
+ this.eventSource = eventSource;
+ this.expectedCommentTypes = expectedCommentTypes;
+ commentLineList = new ArrayList<>();
+ }
+
+ /**
+ * Determine if the event is a comment of one of the expected types set during construction.
+ *
+ * @param event the event to test.
+ * @return <code>true</code> if the events is a comment of the expected type; Otherwise, false.
+ */
+ private boolean isEventExpected(Event event) {
+ if (event == null || !event.is(Event.ID.Comment)) {
+ return false;
+ }
+ CommentEvent commentEvent = (CommentEvent) event;
+ for (CommentType type : expectedCommentTypes) {
+ if (commentEvent.getCommentType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Collect all events of the expected type (set during construction) starting with the top event
+ * on the event source. Collection stops as soon as a non comment or comment of the unexpected
+ * type is encountered.
+ *
+ * @return this object.
+ */
+ public CommentEventsCollector collectEvents() {
+ collectEvents(null);
+ return this;
+ }
+
+ /**
+ * Collect all events of the expected type (set during construction) starting with event provided
+ * as an argument and continuing with the top event on the event source. Collection stops as soon
+ * as a non comment or comment of the unexpected type is encountered.
+ *
+ * @param event the first event to attempt to collect.
+ * @return the event provided as an argument, if it is not collected; Otherwise, <code>null</code>
+ */
+ public Event collectEvents(Event event) {
+ if (event != null) {
+ if (isEventExpected(event)) {
+ commentLineList.add(new CommentLine((CommentEvent) event));
+ } else {
+ return event;
+ }
+ }
+ while (isEventExpected(eventSource.peek())) {
+ commentLineList.add(new CommentLine((CommentEvent) eventSource.poll()));
+ }
+ return null;
+ }
+
+ /**
+ * Collect all events of the expected type (set during construction) starting with event provided
+ * as an argument and continuing with the top event on the event source. Collection stops as soon
+ * as a non comment or comment of the unexpected type is encountered.
+ *
+ * @param event the first event to attempt to collect.
+ * @return the event provided as an argument, if it is not collected; Otherwise, the first event
+ * that is not collected.
+ */
+ public Event collectEventsAndPoll(Event event) {
+ Event nextEvent = collectEvents(event);
+ return nextEvent != null ? nextEvent : eventSource.poll();
+ }
+
+ /**
+ * Return the events collected and reset the colletor.
+ *
+ * @return the events collected.
+ */
+ public List<CommentLine> consume() {
+ try {
+ return commentLineList;
+ } finally {
+ commentLineList = new ArrayList<>();
+ }
+ }
+
+ /**
+ * Test if the collector contains any collected events.
+ *
+ * @return <code>true</code> if it does; Otherwise, <code>false</code>
+ */
+ public boolean isEmpty() {
+ return commentLineList.isEmpty();
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java b/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java
new file mode 100644
index 00000000..76ecd1b4
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.comments;
+
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.events.CommentEvent;
+
+/**
+ * A comment line. May be a block comment, blank line, or inline comment.
+ */
+public class CommentLine {
+
+ private final Mark startMark;
+ private final Mark endMark;
+ private final String value;
+ private final CommentType commentType;
+
+ public CommentLine(CommentEvent event) {
+ this(event.getStartMark(), event.getEndMark(), event.getValue(), event.getCommentType());
+ }
+
+ public CommentLine(Mark startMark, Mark endMark, String value, CommentType commentType) {
+ this.startMark = startMark;
+ this.endMark = endMark;
+ this.value = value;
+ this.commentType = commentType;
+ }
+
+ public Mark getEndMark() {
+ return endMark;
+ }
+
+ public Mark getStartMark() {
+ return startMark;
+ }
+
+ public CommentType getCommentType() {
+ return commentType;
+ }
+
+ /**
+ * Value of this comment.
+ *
+ * @return comment's value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ public String toString() {
+ return "<" + this.getClass().getName() + " (type=" + getCommentType() + ", value=" + getValue()
+ + ")>";
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentType.java b/src/main/java/org/yaml/snakeyaml/comments/CommentType.java
new file mode 100644
index 00000000..5ee7cf47
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/comments/CommentType.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.comments;
+
+/**
+ * The type of a comment line.
+ */
+public enum CommentType {
+ BLANK_LINE, //
+ BLOCK, //
+ IN_LINE //
+}
diff --git a/src/main/java/org/yaml/snakeyaml/composer/Composer.java b/src/main/java/org/yaml/snakeyaml/composer/Composer.java
index f8223c29..d89aa234 100644
--- a/src/main/java/org/yaml/snakeyaml/composer/Composer.java
+++ b/src/main/java/org/yaml/snakeyaml/composer/Composer.java
@@ -1,27 +1,32 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.composer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
+import org.yaml.snakeyaml.DumperOptions.FlowStyle;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.comments.CommentEventsCollector;
+import org.yaml.snakeyaml.comments.CommentLine;
+import org.yaml.snakeyaml.comments.CommentType;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.MappingStartEvent;
@@ -41,208 +46,299 @@ import org.yaml.snakeyaml.resolver.Resolver;
/**
* Creates a node graph from parser events.
* <p>
- * Corresponds to the 'Compose' step as described in chapter 3.1 of the <a
- * href="http://yaml.org/spec/1.1/">YAML Specification</a>.
+ * Corresponds to the 'Compose' step as described in chapter 3.1 of the
+ * <a href="http://yaml.org/spec/1.1/">YAML Specification</a>.
* </p>
*/
public class Composer {
- protected final Parser parser;
- private final Resolver resolver;
- private final Map<String, Node> anchors;
- private final Set<Node> recursiveNodes;
-
- public Composer(Parser parser, Resolver resolver) {
- this.parser = parser;
- this.resolver = resolver;
- this.anchors = new HashMap<String, Node>();
- this.recursiveNodes = new HashSet<Node>();
- }
-
- /**
- * Checks if further documents are available.
- *
- * @return <code>true</code> if there is at least one more document.
- */
- public boolean checkNode() {
- // Drop the STREAM-START event.
- if (parser.checkEvent(Event.ID.StreamStart)) {
- parser.getEvent();
- }
- // If there are more documents available?
- return !parser.checkEvent(Event.ID.StreamEnd);
- }
-
- /**
- * Reads and composes the next document.
- *
- * @return The root node of the document or <code>null</code> if no more
- * documents are available.
- */
- public Node getNode() {
- // Get the root node of the next document.
- if (!parser.checkEvent(Event.ID.StreamEnd)) {
- return composeDocument();
- } else {
- return null;
- }
+
+ protected final Parser parser;
+ private final Resolver resolver;
+ private final Map<String, Node> anchors;
+ private final Set<Node> recursiveNodes;
+ private int nonScalarAliasesCount = 0;
+ private final LoaderOptions loadingConfig;
+ private final CommentEventsCollector blockCommentsCollector;
+ private final CommentEventsCollector inlineCommentsCollector;
+ // keep the nesting of collections inside other collections
+ private int nestingDepth = 0;
+ private final int nestingDepthLimit;
+
+ public Composer(Parser parser, Resolver resolver) {
+ this(parser, resolver, new LoaderOptions());
+ }
+
+ public Composer(Parser parser, Resolver resolver, LoaderOptions loadingConfig) {
+ this.parser = parser;
+ this.resolver = resolver;
+ this.anchors = new HashMap<String, Node>();
+ this.recursiveNodes = new HashSet<Node>();
+ this.loadingConfig = loadingConfig;
+ this.blockCommentsCollector =
+ new CommentEventsCollector(parser, CommentType.BLANK_LINE, CommentType.BLOCK);
+ this.inlineCommentsCollector = new CommentEventsCollector(parser, CommentType.IN_LINE);
+ nestingDepthLimit = loadingConfig.getNestingDepthLimit();
+ }
+
+ /**
+ * Checks if further documents are available.
+ *
+ * @return <code>true</code> if there is at least one more document.
+ */
+ public boolean checkNode() {
+ // Drop the STREAM-START event.
+ if (parser.checkEvent(Event.ID.StreamStart)) {
+ parser.getEvent();
}
+ // If there are more documents available?
+ return !parser.checkEvent(Event.ID.StreamEnd);
+ }
- /**
- * Reads a document from a source that contains only one document.
- * <p>
- * If the stream contains more than one document an exception is thrown.
- * </p>
- *
- * @return The root node of the document or <code>null</code> if no document
- * is available.
- */
- public Node getSingleNode() {
- // Drop the STREAM-START event.
- parser.getEvent();
- // Compose a document if the stream is not empty.
- Node document = null;
- if (!parser.checkEvent(Event.ID.StreamEnd)) {
- document = composeDocument();
- }
- // Ensure that the stream contains no more documents.
- if (!parser.checkEvent(Event.ID.StreamEnd)) {
- Event event = parser.getEvent();
- throw new ComposerException("expected a single document in the stream",
- document.getStartMark(), "but found another document", event.getStartMark());
- }
- // Drop the STREAM-END event.
- parser.getEvent();
- return document;
- }
-
- private Node composeDocument() {
- // Drop the DOCUMENT-START event.
- parser.getEvent();
- // Compose the root node.
- Node node = composeNode(null);
- // Drop the DOCUMENT-END event.
- parser.getEvent();
- this.anchors.clear();
- recursiveNodes.clear();
- return node;
- }
-
- private Node composeNode(Node parent) {
- recursiveNodes.add(parent);
- if (parser.checkEvent(Event.ID.Alias)) {
- AliasEvent event = (AliasEvent) parser.getEvent();
- String anchor = event.getAnchor();
- if (!anchors.containsKey(anchor)) {
- throw new ComposerException(null, null, "found undefined alias " + anchor,
- event.getStartMark());
- }
- Node result = anchors.get(anchor);
- if (recursiveNodes.remove(result)) {
- result.setTwoStepsConstruction(true);
- }
- return result;
- }
- NodeEvent event = (NodeEvent) parser.peekEvent();
- String anchor = null;
- anchor = event.getAnchor();
- // the check for duplicate anchors has been removed (issue 174)
- Node node = null;
- if (parser.checkEvent(Event.ID.Scalar)) {
- node = composeScalarNode(anchor);
- } else if (parser.checkEvent(Event.ID.SequenceStart)) {
- node = composeSequenceNode(anchor);
- } else {
- node = composeMappingNode(anchor);
- }
- recursiveNodes.remove(parent);
- return node;
- }
-
- protected Node composeScalarNode(String anchor) {
- ScalarEvent ev = (ScalarEvent) parser.getEvent();
- String tag = ev.getTag();
- boolean resolved = false;
- Tag nodeTag;
- if (tag == null || tag.equals("!")) {
- nodeTag = resolver.resolve(NodeId.scalar, ev.getValue(), ev.getImplicit()
- .canOmitTagInPlainScalar());
- resolved = true;
- } else {
- nodeTag = new Tag(tag);
- }
- Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(),
- ev.getEndMark(), ev.getStyle());
- if (anchor != null) {
- anchors.put(anchor, node);
- }
- return node;
- }
-
- protected Node composeSequenceNode(String anchor) {
- SequenceStartEvent startEvent = (SequenceStartEvent) parser.getEvent();
- String tag = startEvent.getTag();
- Tag nodeTag;
- boolean resolved = false;
- if (tag == null || tag.equals("!")) {
- nodeTag = resolver.resolve(NodeId.sequence, null, startEvent.getImplicit());
- resolved = true;
- } else {
- nodeTag = new Tag(tag);
- }
- final ArrayList<Node> children = new ArrayList<Node>();
- SequenceNode node = new SequenceNode(nodeTag, resolved, children,
- startEvent.getStartMark(), null, startEvent.getFlowStyle());
- if (anchor != null) {
- anchors.put(anchor, node);
- }
- while (!parser.checkEvent(Event.ID.SequenceEnd)) {
- children.add(composeNode(node));
- }
- Event endEvent = parser.getEvent();
- node.setEndMark(endEvent.getEndMark());
- return node;
- }
-
- protected Node composeMappingNode(String anchor) {
- MappingStartEvent startEvent = (MappingStartEvent) parser.getEvent();
- String tag = startEvent.getTag();
- Tag nodeTag;
- boolean resolved = false;
- if (tag == null || tag.equals("!")) {
- nodeTag = resolver.resolve(NodeId.mapping, null, startEvent.getImplicit());
- resolved = true;
- } else {
- nodeTag = new Tag(tag);
- }
+ /**
+ * Reads and composes the next document.
+ *
+ * @return The root node of the document or <code>null</code> if no more documents are available.
+ */
+ public Node getNode() {
+ // Collect inter-document start comments
+ blockCommentsCollector.collectEvents();
+ if (parser.checkEvent(Event.ID.StreamEnd)) {
+ List<CommentLine> commentLines = blockCommentsCollector.consume();
+ Mark startMark = commentLines.get(0).getStartMark();
+ List<NodeTuple> children = Collections.emptyList();
+ Node node = new MappingNode(Tag.COMMENT, false, children, startMark, null, FlowStyle.BLOCK);
+ node.setBlockComments(commentLines);
+ return node;
+ }
+ // Drop the DOCUMENT-START event.
+ parser.getEvent();
+ // Compose the root node.
+ Node node = composeNode(null);
+ // Drop the DOCUMENT-END event.
+ blockCommentsCollector.collectEvents();
+ if (!blockCommentsCollector.isEmpty()) {
+ node.setEndComments(blockCommentsCollector.consume());
+ }
+ parser.getEvent();
+ this.anchors.clear();
+ this.recursiveNodes.clear();
+ return node;
+ }
- final List<NodeTuple> children = new ArrayList<NodeTuple>();
- MappingNode node = new MappingNode(nodeTag, resolved, children, startEvent.getStartMark(),
- null, startEvent.getFlowStyle());
- if (anchor != null) {
- anchors.put(anchor, node);
- }
- while (!parser.checkEvent(Event.ID.MappingEnd)) {
- composeMappingChildren(children, node);
- }
- Event endEvent = parser.getEvent();
- node.setEndMark(endEvent.getEndMark());
- return node;
+ /**
+ * Reads a document from a source that contains only one document.
+ * <p>
+ * If the stream contains more than one document an exception is thrown.
+ * </p>
+ *
+ * @return The root node of the document or <code>null</code> if no document is available.
+ */
+ public Node getSingleNode() {
+ // Drop the STREAM-START event.
+ parser.getEvent();
+ // Compose a document if the stream is not empty.
+ Node document = null;
+ if (!parser.checkEvent(Event.ID.StreamEnd)) {
+ document = getNode();
}
+ // Ensure that the stream contains no more documents.
+ if (!parser.checkEvent(Event.ID.StreamEnd)) {
+ Event event = parser.getEvent();
+ Mark contextMark = document != null ? document.getStartMark() : null;
+ throw new ComposerException("expected a single document in the stream", contextMark,
+ "but found another document", event.getStartMark());
+ }
+ // Drop the STREAM-END event.
+ parser.getEvent();
+ return document;
+ }
- protected void composeMappingChildren(List<NodeTuple> children, MappingNode node) {
- Node itemKey = composeKeyNode(node);
- if (itemKey.getTag().equals(Tag.MERGE)) {
- node.setMerged(true);
+ private Node composeNode(Node parent) {
+ blockCommentsCollector.collectEvents();
+ if (parent != null) {
+ recursiveNodes.add(parent);
+ }
+ final Node node;
+ if (parser.checkEvent(Event.ID.Alias)) {
+ AliasEvent event = (AliasEvent) parser.getEvent();
+ String anchor = event.getAnchor();
+ if (!anchors.containsKey(anchor)) {
+ throw new ComposerException(null, null, "found undefined alias " + anchor,
+ event.getStartMark());
+ }
+ node = anchors.get(anchor);
+ if (!(node instanceof ScalarNode)) {
+ this.nonScalarAliasesCount++;
+ if (this.nonScalarAliasesCount > loadingConfig.getMaxAliasesForCollections()) {
+ throw new YAMLException(
+ "Number of aliases for non-scalar nodes exceeds the specified max="
+ + loadingConfig.getMaxAliasesForCollections());
}
- Node itemValue = composeValueNode(node);
- children.add(new NodeTuple(itemKey, itemValue));
+ }
+ if (recursiveNodes.remove(node)) {
+ node.setTwoStepsConstruction(true);
+ }
+ // drop comments, they can not be supported here
+ blockCommentsCollector.consume();
+ inlineCommentsCollector.collectEvents().consume();
+ } else {
+ NodeEvent event = (NodeEvent) parser.peekEvent();
+ String anchor = event.getAnchor();
+ increaseNestingDepth();
+ // the check for duplicate anchors has been removed (issue 174)
+ if (parser.checkEvent(Event.ID.Scalar)) {
+ node = composeScalarNode(anchor, blockCommentsCollector.consume());
+ } else if (parser.checkEvent(Event.ID.SequenceStart)) {
+ node = composeSequenceNode(anchor);
+ } else {
+ node = composeMappingNode(anchor);
+ }
+ decreaseNestingDepth();
}
+ recursiveNodes.remove(parent);
+ return node;
+ }
+
+ protected Node composeScalarNode(String anchor, List<CommentLine> blockComments) {
+ ScalarEvent ev = (ScalarEvent) parser.getEvent();
+ String tag = ev.getTag();
+ boolean resolved = false;
+ Tag nodeTag;
+ if (tag == null || tag.equals("!")) {
+ nodeTag = resolver.resolve(NodeId.scalar, ev.getValue(),
+ ev.getImplicit().canOmitTagInPlainScalar());
+ resolved = true;
+ } else {
+ nodeTag = new Tag(tag);
+ }
+ Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(), ev.getEndMark(),
+ ev.getScalarStyle());
+ if (anchor != null) {
+ node.setAnchor(anchor);
+ anchors.put(anchor, node);
+ }
+ node.setBlockComments(blockComments);
+ node.setInLineComments(inlineCommentsCollector.collectEvents().consume());
+ return node;
+ }
+
+ protected Node composeSequenceNode(String anchor) {
+ SequenceStartEvent startEvent = (SequenceStartEvent) parser.getEvent();
+ String tag = startEvent.getTag();
+ Tag nodeTag;
+
+ boolean resolved = false;
+ if (tag == null || tag.equals("!")) {
+ nodeTag = resolver.resolve(NodeId.sequence, null, startEvent.getImplicit());
+ resolved = true;
+ } else {
+ nodeTag = new Tag(tag);
+ }
+ final ArrayList<Node> children = new ArrayList<Node>();
+ SequenceNode node = new SequenceNode(nodeTag, resolved, children, startEvent.getStartMark(),
+ null, startEvent.getFlowStyle());
+ if (startEvent.isFlow()) {
+ node.setBlockComments(blockCommentsCollector.consume());
+ }
+ if (anchor != null) {
+ node.setAnchor(anchor);
+ anchors.put(anchor, node);
+ }
+ while (!parser.checkEvent(Event.ID.SequenceEnd)) {
+ blockCommentsCollector.collectEvents();
+ if (parser.checkEvent(Event.ID.SequenceEnd)) {
+ break;
+ }
+ children.add(composeNode(node));
+ }
+ if (startEvent.isFlow()) {
+ node.setInLineComments(inlineCommentsCollector.collectEvents().consume());
+ }
+ Event endEvent = parser.getEvent();
+ node.setEndMark(endEvent.getEndMark());
+ inlineCommentsCollector.collectEvents();
+ if (!inlineCommentsCollector.isEmpty()) {
+ node.setInLineComments(inlineCommentsCollector.consume());
+ }
+ return node;
+ }
+
+ protected Node composeMappingNode(String anchor) {
+ MappingStartEvent startEvent = (MappingStartEvent) parser.getEvent();
+ String tag = startEvent.getTag();
+ Tag nodeTag;
+ boolean resolved = false;
+ if (tag == null || tag.equals("!")) {
+ nodeTag = resolver.resolve(NodeId.mapping, null, startEvent.getImplicit());
+ resolved = true;
+ } else {
+ nodeTag = new Tag(tag);
+ }
+
+ final List<NodeTuple> children = new ArrayList<NodeTuple>();
+ MappingNode node = new MappingNode(nodeTag, resolved, children, startEvent.getStartMark(), null,
+ startEvent.getFlowStyle());
+ if (startEvent.isFlow()) {
+ node.setBlockComments(blockCommentsCollector.consume());
+ }
+ if (anchor != null) {
+ node.setAnchor(anchor);
+ anchors.put(anchor, node);
+ }
+ while (!parser.checkEvent(Event.ID.MappingEnd)) {
+ blockCommentsCollector.collectEvents();
+ if (parser.checkEvent(Event.ID.MappingEnd)) {
+ break;
+ }
+ composeMappingChildren(children, node);
+ }
+ if (startEvent.isFlow()) {
+ node.setInLineComments(inlineCommentsCollector.collectEvents().consume());
+ }
+ Event endEvent = parser.getEvent();
+ node.setEndMark(endEvent.getEndMark());
+ inlineCommentsCollector.collectEvents();
+ if (!inlineCommentsCollector.isEmpty()) {
+ node.setInLineComments(inlineCommentsCollector.consume());
+ }
+ return node;
+ }
+
+ protected void composeMappingChildren(List<NodeTuple> children, MappingNode node) {
+ Node itemKey = composeKeyNode(node);
+ if (itemKey.getTag().equals(Tag.MERGE)) {
+ node.setMerged(true);
+ }
+ Node itemValue = composeValueNode(node);
+ children.add(new NodeTuple(itemKey, itemValue));
+ }
+
+ protected Node composeKeyNode(MappingNode node) {
+ return composeNode(node);
+ }
+
+ protected Node composeValueNode(MappingNode node) {
+ return composeNode(node);
+ }
- protected Node composeKeyNode(MappingNode node) {
- return composeNode(node);
+ /**
+ * Increase nesting depth and fail when it exceeds the denied limit
+ */
+ private void increaseNestingDepth() {
+ if (nestingDepth > nestingDepthLimit) {
+ throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit);
}
+ nestingDepth++;
+ }
- protected Node composeValueNode(MappingNode node) {
- return composeNode(node);
+ /**
+ * Indicate that the collection is finished and the nesting is decreased
+ */
+ private void decreaseNestingDepth() {
+ if (nestingDepth > 0) {
+ nestingDepth--;
+ } else {
+ throw new YAMLException("Nesting Depth cannot be negative");
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/composer/ComposerException.java b/src/main/java/org/yaml/snakeyaml/composer/ComposerException.java
index 5d20c1d4..8e6cbd7f 100644
--- a/src/main/java/org/yaml/snakeyaml/composer/ComposerException.java
+++ b/src/main/java/org/yaml/snakeyaml/composer/ComposerException.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.composer;
@@ -19,9 +17,10 @@ import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.MarkedYAMLException;
public class ComposerException extends MarkedYAMLException {
- private static final long serialVersionUID = 2146314636913113935L;
- protected ComposerException(String context, Mark contextMark, String problem, Mark problemMark) {
- super(context, contextMark, problem, problemMark);
- }
+ private static final long serialVersionUID = 2146314636913113935L;
+
+ protected ComposerException(String context, Mark contextMark, String problem, Mark problemMark) {
+ super(context, contextMark, problem, problemMark);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java b/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java
index 61d14f60..ff0e3bbd 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
@@ -19,23 +17,22 @@ import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.Node;
/**
- * Because recursive structures are not very common we provide a way to save
- * some typing when extending a constructor
+ * Because recursive structures are not very common we provide a way to save some typing when
+ * extending a constructor
*/
public abstract class AbstractConstruct implements Construct {
- /**
- * Fail with a reminder to provide the seconds step for a recursive
- * structure
- *
- * @see org.yaml.snakeyaml.constructor.Construct#construct2ndStep(org.yaml.snakeyaml.nodes.Node,
- * java.lang.Object)
- */
- public void construct2ndStep(Node node, Object data) {
- if (node.isTwoStepsConstruction()) {
- throw new IllegalStateException("Not Implemented in " + getClass().getName());
- } else {
- throw new YAMLException("Unexpected recursive structure for Node: " + node);
- }
+ /**
+ * Fail with a reminder to provide the seconds step for a recursive structure
+ *
+ * @see org.yaml.snakeyaml.constructor.Construct#construct2ndStep(org.yaml.snakeyaml.nodes.Node,
+ * java.lang.Object)
+ */
+ public void construct2ndStep(Node node, Object data) {
+ if (node.isTwoStepsConstruction()) {
+ throw new IllegalStateException("Not Implemented in " + getClass().getName());
+ } else {
+ throw new YAMLException("Unexpected recursive structure for Node: " + node);
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java
index 3a504cbb..a4ed1f50 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java
@@ -1,21 +1,20 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
import java.lang.reflect.Array;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
@@ -25,12 +24,19 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
-
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.composer.ComposerException;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.PropertyUtils;
+import org.yaml.snakeyaml.nodes.CollectionNode;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
@@ -40,411 +46,578 @@ import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
public abstract class BaseConstructor {
- /**
- * It maps the node kind to the the Construct implementation. When the
- * runtime class is known then the implicit tag is ignored.
- */
- protected final Map<NodeId, Construct> yamlClassConstructors = new EnumMap<NodeId, Construct>(
- NodeId.class);
- /**
- * It maps the (explicit or implicit) tag to the Construct implementation.
- * It is used: <br/>
- * 1) explicit tag - if present. <br/>
- * 2) implicit tag - when the runtime class of the instance is unknown (the
- * node has the Object.class)
- */
- protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>();
- /**
- * It maps the (explicit or implicit) tag to the Construct implementation.
- * It is used when no exact match found.
- */
- protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>();
-
- protected Composer composer;
- private final Map<Node, Object> constructedObjects;
- private final Set<Node> recursiveObjects;
- private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill;
- private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill;
-
- protected Tag rootTag;
- private PropertyUtils propertyUtils;
- private boolean explicitPropertyUtils;
-
- public BaseConstructor() {
- constructedObjects = new HashMap<Node, Object>();
- recursiveObjects = new HashSet<Node>();
- maps2fill = new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>();
- sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>();
- rootTag = null;
- explicitPropertyUtils = false;
- }
- public void setComposer(Composer composer) {
- this.composer = composer;
+ /**
+ * An instance returned by newInstance methods when instantiation has not been performed.
+ */
+ protected static final Object NOT_INSTANTIATED_OBJECT = new Object();
+
+ /**
+ * It maps the node kind to the the Construct implementation. When the runtime class is known then
+ * the implicit tag is ignored.
+ */
+ protected final Map<NodeId, Construct> yamlClassConstructors =
+ new EnumMap<NodeId, Construct>(NodeId.class);
+ /**
+ * It maps the (explicit or implicit) tag to the Construct implementation. It is used: 1) explicit
+ * tag - if present. 2) implicit tag - when the runtime class of the instance is unknown (the node
+ * has the Object.class)
+ */
+ protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>();
+ /**
+ * It maps the (explicit or implicit) tag to the Construct implementation. It is used when no
+ * exact match found.
+ */
+ protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>();
+
+ protected Composer composer;
+ final Map<Node, Object> constructedObjects;
+ private final Set<Node> recursiveObjects;
+ private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill;
+ private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill;
+
+ protected Tag rootTag;
+ private PropertyUtils propertyUtils;
+ private boolean explicitPropertyUtils;
+ private boolean allowDuplicateKeys = true;
+ private boolean wrappedToRootException = false;
+
+ private boolean enumCaseSensitive = false;
+
+ protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
+ protected final Map<Tag, Class<? extends Object>> typeTags;
+
+ protected LoaderOptions loadingConfig;
+
+ public BaseConstructor() {
+ this(new LoaderOptions());
+ }
+
+ public BaseConstructor(LoaderOptions loadingConfig) {
+ constructedObjects = new HashMap<Node, Object>();
+ recursiveObjects = new HashSet<Node>();
+ maps2fill =
+ new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>();
+ sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>();
+ typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
+ typeTags = new HashMap<Tag, Class<? extends Object>>();
+
+ rootTag = null;
+ explicitPropertyUtils = false;
+
+ typeDefinitions.put(SortedMap.class,
+ new TypeDescription(SortedMap.class, Tag.OMAP, TreeMap.class));
+ typeDefinitions.put(SortedSet.class,
+ new TypeDescription(SortedSet.class, Tag.SET, TreeSet.class));
+ this.loadingConfig = loadingConfig;
+ }
+
+ public void setComposer(Composer composer) {
+ this.composer = composer;
+ }
+
+ /**
+ * Check if more documents available
+ *
+ * @return true when there are more YAML documents in the stream
+ */
+ public boolean checkData() {
+ // If there are more documents available?
+ return composer.checkNode();
+ }
+
+ /**
+ * Construct and return the next document
+ *
+ * @return constructed instance
+ */
+ public Object getData() throws NoSuchElementException {
+ // Construct and return the next document.
+ if (!composer.checkNode()) {
+ throw new NoSuchElementException("No document is available.");
}
-
- /**
- * Check if more documents available
- *
- * @return true when there are more YAML documents in the stream
- */
- public boolean checkData() {
- // If there are more documents available?
- return composer.checkNode();
+ Node node = composer.getNode();
+ if (rootTag != null) {
+ node.setTag(rootTag);
}
-
- /**
- * Construct and return the next document
- *
- * @return constructed instance
- */
- public Object getData() {
- // Construct and return the next document.
- composer.checkNode();
- Node node = composer.getNode();
- if (rootTag != null) {
- node.setTag(rootTag);
- }
- return constructDocument(node);
+ return constructDocument(node);
+ }
+
+ /**
+ * Ensure that the stream contains a single document and construct it
+ *
+ * @param type the class of the instance being created
+ * @return constructed instance
+ * @throws ComposerException in case there are more documents in the stream
+ */
+ public Object getSingleData(Class<?> type) {
+ // Ensure that the stream contains a single document and construct it
+ final Node node = composer.getSingleNode();
+ if (node != null && !Tag.NULL.equals(node.getTag())) {
+ if (Object.class != type) {
+ node.setTag(new Tag(type));
+ } else if (rootTag != null) {
+ node.setTag(rootTag);
+ }
+ return constructDocument(node);
+ } else {
+ Construct construct = yamlConstructors.get(Tag.NULL);
+ return construct.construct(node);
}
-
- /**
- * Ensure that the stream contains a single document and construct it
- *
- * @return constructed instance
- * @throws ComposerException
- * in case there are more documents in the stream
- */
- public Object getSingleData(Class<?> type) {
- // Ensure that the stream contains a single document and construct it
- Node node = composer.getSingleNode();
- if (node != null) {
- if (Object.class != type) {
- node.setTag(new Tag(type));
- } else if (rootTag != null) {
- node.setTag(rootTag);
- }
- return constructDocument(node);
- }
- return null;
+ }
+
+ /**
+ * Construct complete YAML document. Call the second step in case of recursive structures. At the
+ * end cleans all the state.
+ *
+ * @param node root Node
+ * @return Java instance
+ */
+ protected final Object constructDocument(Node node) {
+ try {
+ Object data = constructObject(node);
+ fillRecursive();
+ return data;
+ } catch (RuntimeException e) {
+ if (wrappedToRootException && !(e instanceof YAMLException)) {
+ throw new YAMLException(e);
+ } else {
+ throw e;
+ }
+ } finally {
+ // clean up resources
+ constructedObjects.clear();
+ recursiveObjects.clear();
}
-
- /**
- * Construct complete YAML document. Call the second step in case of
- * recursive structures. At the end cleans all the state.
- *
- * @param node
- * root Node
- * @return Java instance
- */
- protected final Object constructDocument(Node node) {
- Object data = constructObject(node);
- fillRecursive();
- constructedObjects.clear();
- recursiveObjects.clear();
- return data;
+ }
+
+ /**
+ * Fill the recursive structures and clean the internal collections
+ */
+ private void fillRecursive() {
+ if (!maps2fill.isEmpty()) {
+ for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) {
+ RecursiveTuple<Object, Object> key_value = entry._2();
+ entry._1().put(key_value._1(), key_value._2());
+ }
+ maps2fill.clear();
}
-
- private void fillRecursive() {
- if (!maps2fill.isEmpty()) {
- for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) {
- RecursiveTuple<Object, Object> key_value = entry._2();
- entry._1().put(key_value._1(), key_value._2());
- }
- maps2fill.clear();
- }
- if (!sets2fill.isEmpty()) {
- for (RecursiveTuple<Set<Object>, Object> value : sets2fill) {
- value._1().add(value._2());
- }
- sets2fill.clear();
- }
+ if (!sets2fill.isEmpty()) {
+ for (RecursiveTuple<Set<Object>, Object> value : sets2fill) {
+ value._1().add(value._2());
+ }
+ sets2fill.clear();
}
-
- /**
- * Construct object from the specified Node. Return existing instance if the
- * node is already constructed.
- *
- * @param node
- * Node to be constructed
- * @return Java instance
- */
- protected Object constructObject(Node node) {
- if (constructedObjects.containsKey(node)) {
- return constructedObjects.get(node);
- }
- if (recursiveObjects.contains(node)) {
- throw new ConstructorException(null, null, "found unconstructable recursive node",
- node.getStartMark());
- }
- recursiveObjects.add(node);
- Construct constructor = getConstructor(node);
- Object data = constructor.construct(node);
- constructedObjects.put(node, data);
- recursiveObjects.remove(node);
- if (node.isTwoStepsConstruction()) {
- constructor.construct2ndStep(node, data);
- }
- return data;
+ }
+
+ /**
+ * Construct object from the specified Node. Return existing instance if the node is already
+ * constructed.
+ *
+ * @param node Node to be constructed
+ * @return Java instance
+ */
+ protected Object constructObject(Node node) {
+ if (constructedObjects.containsKey(node)) {
+ return constructedObjects.get(node);
}
+ return constructObjectNoCheck(node);
+ }
- /**
- * Get the constructor to construct the Node. For implicit tags if the
- * runtime class is known a dedicated Construct implementation is used.
- * Otherwise the constructor is chosen by the tag.
- *
- * @param node
- * Node to be constructed
- * @return Construct implementation for the specified node
- */
- protected Construct getConstructor(Node node) {
- if (node.useClassConstructor()) {
- return yamlClassConstructors.get(node.getNodeId());
- } else {
- Construct constructor = yamlConstructors.get(node.getTag());
- if (constructor == null) {
- for (String prefix : yamlMultiConstructors.keySet()) {
- if (node.getTag().startsWith(prefix)) {
- return yamlMultiConstructors.get(prefix);
- }
- }
- return yamlConstructors.get(null);
- }
- return constructor;
+ protected Object constructObjectNoCheck(Node node) {
+ if (recursiveObjects.contains(node)) {
+ throw new ConstructorException(null, null, "found unconstructable recursive node",
+ node.getStartMark());
+ }
+ recursiveObjects.add(node);
+ Construct constructor = getConstructor(node);
+ Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node)
+ : constructor.construct(node);
+
+ finalizeConstruction(node, data);
+ constructedObjects.put(node, data);
+ recursiveObjects.remove(node);
+ if (node.isTwoStepsConstruction()) {
+ constructor.construct2ndStep(node, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the constructor to construct the Node. For implicit tags if the runtime class is known a
+ * dedicated Construct implementation is used. Otherwise the constructor is chosen by the tag.
+ *
+ * @param node {@link Node} to construct an instance from
+ * @return {@link Construct} implementation for the specified node
+ */
+ protected Construct getConstructor(Node node) {
+ if (node.useClassConstructor()) {
+ return yamlClassConstructors.get(node.getNodeId());
+ } else {
+ Construct constructor = yamlConstructors.get(node.getTag());
+ if (constructor == null) {
+ for (String prefix : yamlMultiConstructors.keySet()) {
+ if (node.getTag().startsWith(prefix)) {
+ return yamlMultiConstructors.get(prefix);
+ }
}
+ return yamlConstructors.get(null);
+ }
+ return constructor;
}
+ }
- protected Object constructScalar(ScalarNode node) {
- return node.getValue();
- }
+ protected String constructScalar(ScalarNode node) {
+ return node.getValue();
+ }
- protected List<Object> createDefaultList(int initSize) {
- return new ArrayList<Object>(initSize);
- }
+ // >>>> DEFAULTS >>>>
+ protected List<Object> createDefaultList(int initSize) {
+ return new ArrayList<Object>(initSize);
+ }
- protected Set<Object> createDefaultSet(int initSize) {
- return new LinkedHashSet<Object>(initSize);
- }
+ protected Set<Object> createDefaultSet(int initSize) {
+ return new LinkedHashSet<Object>(initSize);
+ }
- protected Object createArray(Class<?> type, int size) {
- return Array.newInstance(type.getComponentType(), size);
- }
+ protected Map<Object, Object> createDefaultMap(int initSize) {
+ // respect order from YAML document
+ return new LinkedHashMap<Object, Object>(initSize);
+ }
- @SuppressWarnings("unchecked")
- protected List<? extends Object> constructSequence(SequenceNode node) {
- List<Object> result;
- if (List.class.isAssignableFrom(node.getType()) && !node.getType().isInterface()) {
- // the root class may be defined (Vector for instance)
- try {
- result = (List<Object>) node.getType().newInstance();
- } catch (Exception e) {
- throw new YAMLException(e);
- }
- } else {
- result = createDefaultList(node.getValue().size());
- }
- constructSequenceStep2(node, result);
- return result;
+ protected Object createArray(Class<?> type, int size) {
+ return Array.newInstance(type.getComponentType(), size);
+ }
- }
+ // <<<< DEFAULTS <<<<
- @SuppressWarnings("unchecked")
- protected Set<? extends Object> constructSet(SequenceNode node) {
- Set<Object> result;
- if (!node.getType().isInterface()) {
- // the root class may be defined
- try {
- result = (Set<Object>) node.getType().newInstance();
- } catch (Exception e) {
- throw new YAMLException(e);
- }
- } else {
- result = createDefaultSet(node.getValue().size());
+ protected Object finalizeConstruction(Node node, Object data) {
+ final Class<? extends Object> type = node.getType();
+ if (typeDefinitions.containsKey(type)) {
+ return typeDefinitions.get(type).finalizeConstruction(data);
+ }
+ return data;
+ }
+
+ // >>>> NEW instance
+ protected Object newInstance(Node node) {
+ return newInstance(Object.class, node);
+ }
+
+ protected final Object newInstance(Class<?> ancestor, Node node) {
+ return newInstance(ancestor, node, true);
+ }
+
+ /**
+ * Tries to create a new object for the node.
+ *
+ * @param ancestor expected ancestor of the {@code node.getType()}
+ * @param node for which to create a corresponding java object
+ * @param tryDefault should default constructor to be tried when there is no corresponding
+ * {@code TypeDescription} or {@code TypeDescription.newInstance(node)} returns
+ * {@code null}.
+ *
+ * @return - a new object created for {@code node.getType()} by using corresponding
+ * TypeDescription.newInstance or default constructor. - {@code NOT_INSTANTIATED_OBJECT}
+ * in case no object has been created
+ */
+ protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) {
+ try {
+ final Class<? extends Object> type = node.getType();
+ if (typeDefinitions.containsKey(type)) {
+ TypeDescription td = typeDefinitions.get(type);
+ final Object instance = td.newInstance(node);
+ if (instance != null) {
+ return instance;
}
- constructSequenceStep2(node, result);
- return result;
-
+ }
+
+ if (tryDefault) {
+ /*
+ * Removed <code> have InstantiationException in case of abstract type
+ */
+ if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) {
+ java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor();
+ c.setAccessible(true);
+ return c.newInstance();
+ }
+ }
+ } catch (Exception e) {
+ throw new YAMLException(e);
}
- protected Object constructArray(SequenceNode node) {
- return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
- }
+ return NOT_INSTANTIATED_OBJECT;
+ }
- protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
- for (Node child : node.getValue()) {
- collection.add(constructObject(child));
- }
+ @SuppressWarnings("unchecked")
+ protected Set<Object> newSet(CollectionNode<?> node) {
+ Object instance = newInstance(Set.class, node);
+ if (instance != NOT_INSTANTIATED_OBJECT) {
+ return (Set<Object>) instance;
+ } else {
+ return createDefaultSet(node.getValue().size());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected List<Object> newList(SequenceNode node) {
+ Object instance = newInstance(List.class, node);
+ if (instance != NOT_INSTANTIATED_OBJECT) {
+ return (List<Object>) instance;
+ } else {
+ return createDefaultList(node.getValue().size());
}
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Map<Object, Object> newMap(MappingNode node) {
+ Object instance = newInstance(Map.class, node);
+ if (instance != NOT_INSTANTIATED_OBJECT) {
+ return (Map<Object, Object>) instance;
+ } else {
+ return createDefaultMap(node.getValue().size());
+ }
+ }
+
+ // <<<< NEW instance
+
+ // >>>> Construct => NEW, 2ndStep(filling)
+ protected List<? extends Object> constructSequence(SequenceNode node) {
+ List<Object> result = newList(node);
+ constructSequenceStep2(node, result);
+ return result;
+ }
+
+ protected Set<? extends Object> constructSet(SequenceNode node) {
+ Set<Object> result = newSet(node);
+ constructSequenceStep2(node, result);
+ return result;
+ }
+
+ protected Object constructArray(SequenceNode node) {
+ return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
+ }
+
+ protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
+ for (Node child : node.getValue()) {
+ collection.add(constructObject(child));
+ }
+ }
- protected Object constructArrayStep2(SequenceNode node, Object array) {
- final Class<?> componentType = node.getType().getComponentType();
+ protected Object constructArrayStep2(SequenceNode node, Object array) {
+ final Class<?> componentType = node.getType().getComponentType();
- int index = 0;
- for (Node child : node.getValue()) {
- // Handle multi-dimensional arrays...
- if (child.getType() == Object.class) {
- child.setType(componentType);
- }
+ int index = 0;
+ for (Node child : node.getValue()) {
+ // Handle multi-dimensional arrays...
+ if (child.getType() == Object.class) {
+ child.setType(componentType);
+ }
- final Object value = constructObject(child);
+ final Object value = constructObject(child);
- if (componentType.isPrimitive()) {
- // Null values are disallowed for primitives
- if (value == null) {
- throw new NullPointerException("Unable to construct element value for " + child);
- }
+ if (componentType.isPrimitive()) {
+ // Null values are disallowed for primitives
+ if (value == null) {
+ throw new NullPointerException("Unable to construct element value for " + child);
+ }
- // Primitive arrays require quite a lot of work.
- if (byte.class.equals(componentType)) {
- Array.setByte(array, index, ((Number) value).byteValue());
+ // Primitive arrays require quite a lot of work.
+ if (byte.class.equals(componentType)) {
+ Array.setByte(array, index, ((Number) value).byteValue());
- } else if (short.class.equals(componentType)) {
- Array.setShort(array, index, ((Number) value).shortValue());
+ } else if (short.class.equals(componentType)) {
+ Array.setShort(array, index, ((Number) value).shortValue());
- } else if (int.class.equals(componentType)) {
- Array.setInt(array, index, ((Number) value).intValue());
+ } else if (int.class.equals(componentType)) {
+ Array.setInt(array, index, ((Number) value).intValue());
- } else if (long.class.equals(componentType)) {
- Array.setLong(array, index, ((Number) value).longValue());
+ } else if (long.class.equals(componentType)) {
+ Array.setLong(array, index, ((Number) value).longValue());
- } else if (float.class.equals(componentType)) {
- Array.setFloat(array, index, ((Number) value).floatValue());
+ } else if (float.class.equals(componentType)) {
+ Array.setFloat(array, index, ((Number) value).floatValue());
- } else if (double.class.equals(componentType)) {
- Array.setDouble(array, index, ((Number) value).doubleValue());
+ } else if (double.class.equals(componentType)) {
+ Array.setDouble(array, index, ((Number) value).doubleValue());
- } else if (char.class.equals(componentType)) {
- Array.setChar(array, index, ((Character) value).charValue());
+ } else if (char.class.equals(componentType)) {
+ Array.setChar(array, index, ((Character) value).charValue());
- } else if (boolean.class.equals(componentType)) {
- Array.setBoolean(array, index, ((Boolean) value).booleanValue());
+ } else if (boolean.class.equals(componentType)) {
+ Array.setBoolean(array, index, ((Boolean) value).booleanValue());
- } else {
- throw new YAMLException("unexpected primitive type");
- }
+ } else {
+ throw new YAMLException("unexpected primitive type");
+ }
- } else {
- // Non-primitive arrays can simply be assigned:
- Array.set(array, index, value);
- }
+ } else {
+ // Non-primitive arrays can simply be assigned:
+ Array.set(array, index, value);
+ }
- ++index;
+ ++index;
+ }
+ return array;
+ }
+
+ protected Set<Object> constructSet(MappingNode node) {
+ final Set<Object> set = newSet(node);
+ constructSet2ndStep(node, set);
+ return set;
+ }
+
+ protected Map<Object, Object> constructMapping(MappingNode node) {
+ final Map<Object, Object> mapping = newMap(node);
+ constructMapping2ndStep(node, mapping);
+ return mapping;
+ }
+
+ protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
+ List<NodeTuple> nodeValue = node.getValue();
+ for (NodeTuple tuple : nodeValue) {
+ Node keyNode = tuple.getKeyNode();
+ Node valueNode = tuple.getValueNode();
+ Object key = constructObject(keyNode);
+ if (key != null) {
+ try {
+ key.hashCode();// check circular dependencies
+ } catch (Exception e) {
+ throw new ConstructorException("while constructing a mapping", node.getStartMark(),
+ "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
}
- return array;
+ }
+ Object value = constructObject(valueNode);
+ if (keyNode.isTwoStepsConstruction()) {
+ if (loadingConfig.getAllowRecursiveKeys()) {
+ postponeMapFilling(mapping, key, value);
+ } else {
+ throw new YAMLException(
+ "Recursive key for mapping is detected but it is not configured to be allowed.");
+ }
+ } else {
+ mapping.put(key, value);
+ }
}
-
- protected Map<Object, Object> createDefaultMap() {
- // respect order from YAML document
- return new LinkedHashMap<Object, Object>();
+ }
+
+ /*
+ * if keyObject is created it 2 steps we should postpone putting it in map because it may have
+ * different hash after initialization compared to clean just created one. And map of course does
+ * not observe key hashCode changes.
+ */
+ protected void postponeMapFilling(Map<Object, Object> mapping, Object key, Object value) {
+ maps2fill.add(0, new RecursiveTuple(mapping, new RecursiveTuple(key, value)));
+ }
+
+ protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
+ List<NodeTuple> nodeValue = node.getValue();
+ for (NodeTuple tuple : nodeValue) {
+ Node keyNode = tuple.getKeyNode();
+ Object key = constructObject(keyNode);
+ if (key != null) {
+ try {
+ key.hashCode();// check circular dependencies
+ } catch (Exception e) {
+ throw new ConstructorException("while constructing a Set", node.getStartMark(),
+ "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
+ }
+ }
+ if (keyNode.isTwoStepsConstruction()) {
+ postponeSetFilling(set, key);
+ } else {
+ set.add(key);
+ }
}
-
- protected Set<Object> createDefaultSet() {
- // respect order from YAML document
- return new LinkedHashSet<Object>();
+ }
+
+ /*
+ * if keyObject is created it 2 steps we should postpone putting it into the set because it may
+ * have different hash after initialization compared to clean just created one. And set of course
+ * does not observe value hashCode changes.
+ */
+ protected void postponeSetFilling(Set<Object> set, Object key) {
+ sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key));
+ }
+
+ public void setPropertyUtils(PropertyUtils propertyUtils) {
+ this.propertyUtils = propertyUtils;
+ explicitPropertyUtils = true;
+ Collection<TypeDescription> tds = typeDefinitions.values();
+ for (TypeDescription typeDescription : tds) {
+ typeDescription.setPropertyUtils(propertyUtils);
}
+ }
- protected Set<Object> constructSet(MappingNode node) {
- Set<Object> set = createDefaultSet();
- constructSet2ndStep(node, set);
- return set;
+ public final PropertyUtils getPropertyUtils() {
+ if (propertyUtils == null) {
+ propertyUtils = new PropertyUtils();
}
-
- protected Map<Object, Object> constructMapping(MappingNode node) {
- Map<Object, Object> mapping = createDefaultMap();
- constructMapping2ndStep(node, mapping);
- return mapping;
+ return propertyUtils;
+ }
+
+ /**
+ * Make YAML aware how to parse a custom Class. If there is no root Class assigned in constructor
+ * then the 'root' property of this definition is respected.
+ *
+ * @param definition to be added to the Constructor
+ * @return the previous value associated with <code>definition</code>, or <code>null</code> if
+ * there was no mapping for <code>definition</code>.
+ */
+ public TypeDescription addTypeDescription(TypeDescription definition) {
+ if (definition == null) {
+ throw new NullPointerException("TypeDescription is required.");
}
+ Tag tag = definition.getTag();
+ typeTags.put(tag, definition.getType());
+ definition.setPropertyUtils(getPropertyUtils());
+ return typeDefinitions.put(definition.getType(), definition);
+ }
- protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
- List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
- for (NodeTuple tuple : nodeValue) {
- Node keyNode = tuple.getKeyNode();
- Node valueNode = tuple.getValueNode();
- Object key = constructObject(keyNode);
- if (key != null) {
- try {
- key.hashCode();// check circular dependencies
- } catch (Exception e) {
- throw new ConstructorException("while constructing a mapping",
- node.getStartMark(), "found unacceptable key " + key, tuple
- .getKeyNode().getStartMark(), e);
- }
- }
- Object value = constructObject(valueNode);
- if (keyNode.isTwoStepsConstruction()) {
- /*
- * if keyObject is created it 2 steps we should postpone putting
- * it in map because it may have different hash after
- * initialization compared to clean just created one. And map of
- * course does not observe key hashCode changes.
- */
- maps2fill.add(0,
- new RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>(
- mapping, new RecursiveTuple<Object, Object>(key, value)));
- } else {
- mapping.put(key, value);
- }
- }
- }
+ private static class RecursiveTuple<T, K> {
- protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
- List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
- for (NodeTuple tuple : nodeValue) {
- Node keyNode = tuple.getKeyNode();
- Object key = constructObject(keyNode);
- if (key != null) {
- try {
- key.hashCode();// check circular dependencies
- } catch (Exception e) {
- throw new ConstructorException("while constructing a Set", node.getStartMark(),
- "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
- }
- }
- if (keyNode.isTwoStepsConstruction()) {
- /*
- * if keyObject is created it 2 steps we should postpone putting
- * it into the set because it may have different hash after
- * initialization compared to clean just created one. And set of
- * course does not observe value hashCode changes.
- */
- sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key));
- } else {
- set.add(key);
- }
- }
+ private final T _1;
+ private final K _2;
+
+ public RecursiveTuple(T _1, K _2) {
+ this._1 = _1;
+ this._2 = _2;
}
- public void setPropertyUtils(PropertyUtils propertyUtils) {
- this.propertyUtils = propertyUtils;
- explicitPropertyUtils = true;
+ public K _2() {
+ return _2;
}
- public final PropertyUtils getPropertyUtils() {
- if (propertyUtils == null) {
- propertyUtils = new PropertyUtils();
- }
- return propertyUtils;
+ public T _1() {
+ return _1;
}
+ }
- private static class RecursiveTuple<T, K> {
- private final T _1;
- private final K _2;
+ public final boolean isExplicitPropertyUtils() {
+ return explicitPropertyUtils;
+ }
- public RecursiveTuple(T _1, K _2) {
- this._1 = _1;
- this._2 = _2;
- }
+ public boolean isAllowDuplicateKeys() {
+ return allowDuplicateKeys;
+ }
- public K _2() {
- return _2;
- }
+ public void setAllowDuplicateKeys(boolean allowDuplicateKeys) {
+ this.allowDuplicateKeys = allowDuplicateKeys;
+ }
- public T _1() {
- return _1;
- }
- }
+ public boolean isWrappedToRootException() {
+ return wrappedToRootException;
+ }
- public final boolean isExplicitPropertyUtils() {
- return explicitPropertyUtils;
- }
+ public void setWrappedToRootException(boolean wrappedToRootException) {
+ this.wrappedToRootException = wrappedToRootException;
+ }
+
+ public boolean isEnumCaseSensitive() {
+ return enumCaseSensitive;
+ }
+
+ public void setEnumCaseSensitive(boolean enumCaseSensitive) {
+ this.enumCaseSensitive = enumCaseSensitive;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/Construct.java b/src/main/java/org/yaml/snakeyaml/constructor/Construct.java
index 4fa91186..81ef4446 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/Construct.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/Construct.java
@@ -1,50 +1,43 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
import org.yaml.snakeyaml.nodes.Node;
/**
- * Provide a way to construct a Java instance out of the composed Node. Support
- * recursive objects if it is required. (create Native Data Structure out of
- * Node Graph)
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859109">Chapter 3. Processing YAML
- * Information</a>
+ * Provide a way to construct a Java instance out of the composed Node. Support recursive objects if
+ * it is required. (create Native Data Structure out of Node Graph)
+ *
+ * @see <a href="http://yaml.org/spec/1.1/#id859109">Chapter 3. Processing YAML Information</a>
*/
public interface Construct {
- /**
- * Construct a Java instance with all the properties injected when it is
- * possible.
- *
- * @param node
- * composed Node
- * @return a complete Java instance
- */
- Object construct(Node node);
- /**
- * Apply the second step when constructing recursive structures. Because the
- * instance is already created it can assign a reference to itself.
- *
- * @param node
- * composed Node
- * @param object
- * the instance constructed earlier by
- * <code>construct(Node node)</code> for the provided Node
- */
- void construct2ndStep(Node node, Object object);
+ /**
+ * Construct a Java instance with all the properties injected when it is possible.
+ *
+ * @param node composed Node
+ * @return a complete Java instance
+ */
+ Object construct(Node node);
+
+ /**
+ * Apply the second step when constructing recursive structures. Because the instance is already
+ * created it can assign a reference to itself.
+ *
+ * @param node composed Node
+ * @param object the instance constructed earlier by <code>construct(Node node)</code> for the
+ * provided Node
+ */
+ void construct2ndStep(Node node, Object object);
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java
index 943702f2..52dd5da8 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java
@@ -1,38 +1,29 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
-import java.beans.IntrospectionException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
import java.util.UUID;
-
+import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.Property;
@@ -43,640 +34,635 @@ import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.util.EnumUtils;
/**
* Construct a custom Java instance.
*/
public class Constructor extends SafeConstructor {
- private final Map<Tag, Class<? extends Object>> typeTags;
- protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
- public Constructor() {
- this(Object.class);
+ public Constructor() {
+ this(Object.class);
+ }
+
+ public Constructor(LoaderOptions loadingConfig) {
+ this(Object.class, loadingConfig);
+ }
+
+ /**
+ * Create Constructor for the specified class as the root.
+ *
+ * @param theRoot - the class (usually JavaBean) to be constructed
+ */
+ public Constructor(Class<? extends Object> theRoot) {
+ this(new TypeDescription(checkRoot(theRoot)));
+ }
+
+ public Constructor(Class<? extends Object> theRoot, LoaderOptions loadingConfig) {
+ this(new TypeDescription(checkRoot(theRoot)), loadingConfig);
+ }
+
+ /**
+ * Ugly Java way to check the argument in the constructor
+ */
+ private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
+ if (theRoot == null) {
+ throw new NullPointerException("Root class must be provided.");
+ } else {
+ return theRoot;
}
-
- /**
- * Create Constructor for the specified class as the root.
- *
- * @param theRoot
- * - the class (usually JavaBean) to be constructed
- */
- public Constructor(Class<? extends Object> theRoot) {
- this(new TypeDescription(checkRoot(theRoot)));
+ }
+
+ public Constructor(TypeDescription theRoot) {
+ this(theRoot, null, new LoaderOptions());
+ }
+
+ public Constructor(TypeDescription theRoot, LoaderOptions loadingConfig) {
+ this(theRoot, null, loadingConfig);
+ }
+
+ public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs) {
+ this(theRoot, moreTDs, new LoaderOptions());
+ }
+
+ /**
+ * Create with all possible arguments
+ *
+ * @param theRoot - the class (usually JavaBean) to be constructed
+ * @param moreTDs - collection of classes used by the root class
+ * @param loadingConfig - configuration
+ */
+ public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs,
+ LoaderOptions loadingConfig) {
+ super(loadingConfig);
+ if (theRoot == null) {
+ throw new NullPointerException("Root type must be provided.");
}
-
- /**
- * Ugly Java way to check the argument in the constructor
- */
- private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
- if (theRoot == null) {
- throw new NullPointerException("Root class must be provided.");
- } else
- return theRoot;
+ this.yamlConstructors.put(null, new ConstructYamlObject());
+ if (!Object.class.equals(theRoot.getType())) {
+ rootTag = new Tag(theRoot.getType());
}
-
- public Constructor(TypeDescription theRoot) {
- if (theRoot == null) {
- throw new NullPointerException("Root type must be provided.");
- }
- this.yamlConstructors.put(null, new ConstructYamlObject());
- if (!Object.class.equals(theRoot.getType())) {
- rootTag = new Tag(theRoot.getType());
- }
- typeTags = new HashMap<Tag, Class<? extends Object>>();
- typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
- yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
- yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
- yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
- addTypeDescription(theRoot);
+ yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
+ yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
+ yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
+ addTypeDescription(theRoot);
+ if (moreTDs != null) {
+ for (TypeDescription td : moreTDs) {
+ addTypeDescription(td);
+ }
}
+ }
+
+ /**
+ * Create Constructor for a class which does not have to be in the classpath or for a definition
+ * from a Spring ApplicationContext.
+ *
+ * @param theRoot fully qualified class name of the root class (usually JavaBean)
+ * @throws ClassNotFoundException if cannot be loaded by the classloader
+ */
+ public Constructor(String theRoot) throws ClassNotFoundException {
+ this(Class.forName(check(theRoot)));
+ }
+
+ public Constructor(String theRoot, LoaderOptions loadingConfig) throws ClassNotFoundException {
+ this(Class.forName(check(theRoot)), loadingConfig);
+ }
+
+ private static final String check(String s) {
+ if (s == null) {
+ throw new NullPointerException("Root type must be provided.");
+ }
+ if (s.trim().length() == 0) {
+ throw new YAMLException("Root type must be provided.");
+ }
+ return s;
+ }
+
+ /**
+ * Construct mapping instance (Map, JavaBean) when the runtime class is known.
+ */
+ protected class ConstructMapping implements Construct {
/**
- * Create Constructor for a class which does not have to be in the classpath
- * or for a definition from a Spring ApplicationContext.
- *
- * @param theRoot
- * fully qualified class name of the root class (usually
- * JavaBean)
- * @throws ClassNotFoundException
+ * Construct JavaBean. If type safe collections are used please look at
+ * <code>TypeDescription</code>.
+ *
+ * @param node node where the keys are property names (they can only be <code>String</code>s)
+ * and values are objects to be created
+ * @return constructed JavaBean
*/
- public Constructor(String theRoot) throws ClassNotFoundException {
- this(Class.forName(check(theRoot)));
- }
-
- private static final String check(String s) {
- if (s == null) {
- throw new NullPointerException("Root type must be provided.");
+ public Object construct(Node node) {
+ MappingNode mnode = (MappingNode) node;
+ if (Map.class.isAssignableFrom(node.getType())) {
+ if (node.isTwoStepsConstruction()) {
+ return newMap(mnode);
+ } else {
+ return constructMapping(mnode);
}
- if (s.trim().length() == 0) {
- throw new YAMLException("Root type must be provided.");
+ } else if (Collection.class.isAssignableFrom(node.getType())) {
+ if (node.isTwoStepsConstruction()) {
+ return newSet(mnode);
+ } else {
+ return constructSet(mnode);
}
- return s;
+ } else {
+ Object obj = Constructor.this.newInstance(mnode);
+ if (obj != NOT_INSTANTIATED_OBJECT) {
+ if (node.isTwoStepsConstruction()) {
+ return obj;
+ } else {
+ return constructJavaBean2ndStep(mnode, obj);
+ }
+ } else {
+ throw new ConstructorException(null, null,
+ "Can't create an instance for " + mnode.getTag(), node.getStartMark());
+ }
+ }
}
- /**
- * Make YAML aware how to parse a custom Class. If there is no root Class
- * assigned in constructor then the 'root' property of this definition is
- * respected.
- *
- * @param definition
- * to be added to the Constructor
- * @return the previous value associated with <tt>definition</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>definition</tt>.
- */
- public TypeDescription addTypeDescription(TypeDescription definition) {
- if (definition == null) {
- throw new NullPointerException("TypeDescription is required.");
- }
- Tag tag = definition.getTag();
- typeTags.put(tag, definition.getType());
- return typeDefinitions.put(definition.getType(), definition);
+ @SuppressWarnings("unchecked")
+ public void construct2ndStep(Node node, Object object) {
+ if (Map.class.isAssignableFrom(node.getType())) {
+ constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
+ } else if (Set.class.isAssignableFrom(node.getType())) {
+ constructSet2ndStep((MappingNode) node, (Set<Object>) object);
+ } else {
+ constructJavaBean2ndStep((MappingNode) node, object);
+ }
}
- /**
- * Construct mapping instance (Map, JavaBean) when the runtime class is
- * known.
- */
- protected class ConstructMapping implements Construct {
-
- /**
- * Construct JavaBean. If type safe collections are used please look at
- * <code>TypeDescription</code>.
- *
- * @param node
- * node where the keys are property names (they can only be
- * <code>String</code>s) and values are objects to be created
- * @return constructed JavaBean
- */
- public Object construct(Node node) {
- MappingNode mnode = (MappingNode) node;
- if (Properties.class.isAssignableFrom(node.getType())) {
- Properties properties = new Properties();
- if (!node.isTwoStepsConstruction()) {
- constructMapping2ndStep(mnode, properties);
- } else {
- throw new YAMLException("Properties must not be recursive.");
- }
- return properties;
- } else if (SortedMap.class.isAssignableFrom(node.getType())) {
- SortedMap<Object, Object> map = new TreeMap<Object, Object>();
- if (!node.isTwoStepsConstruction()) {
- constructMapping2ndStep(mnode, map);
- }
- return map;
- } else if (Map.class.isAssignableFrom(node.getType())) {
- if (node.isTwoStepsConstruction()) {
- return createDefaultMap();
- } else {
- return constructMapping(mnode);
- }
- } else if (SortedSet.class.isAssignableFrom(node.getType())) {
- SortedSet<Object> set = new TreeSet<Object>();
- // XXX why this is not used ?
- // if (!node.isTwoStepsConstruction()) {
- constructSet2ndStep(mnode, set);
- // }
- return set;
- } else if (Collection.class.isAssignableFrom(node.getType())) {
- if (node.isTwoStepsConstruction()) {
- return createDefaultSet();
- } else {
- return constructSet(mnode);
- }
- } else {
- if (node.isTwoStepsConstruction()) {
- return createEmptyJavaBean(mnode);
- } else {
- return constructJavaBean2ndStep(mnode, createEmptyJavaBean(mnode));
- }
+ // protected Object createEmptyJavaBean(MappingNode node) {
+ // try {
+ // Object instance = Constructor.this.newInstance(node);
+ // if (instance != null) {
+ // return instance;
+ // }
+ //
+ // /**
+ // * Using only default constructor. Everything else will be
+ // * initialized on 2nd step. If we do here some partial
+ // * initialization, how do we then track what need to be done on
+ // * 2nd step? I think it is better to get only object here (to
+ // * have it as reference for recursion) and do all other thing on
+ // * 2nd step.
+ // */
+ // java.lang.reflect.Constructor<?> c =
+ // node.getType().getDeclaredConstructor();
+ // c.setAccessible(true);
+ // return c.newInstance();
+ // } catch (Exception e) {
+ // throw new YAMLException(e);
+ // }
+ // }
+
+ protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
+ flattenMapping(node, true);
+ Class<? extends Object> beanType = node.getType();
+ List<NodeTuple> nodeValue = node.getValue();
+ for (NodeTuple tuple : nodeValue) {
+ Node valueNode = tuple.getValueNode();
+ // flattenMapping enforces keys to be Strings
+ String key = (String) constructObject(tuple.getKeyNode());
+ try {
+ TypeDescription memberDescription = typeDefinitions.get(beanType);
+ Property property = memberDescription == null ? getProperty(beanType, key)
+ : memberDescription.getProperty(key);
+
+ if (!property.isWritable()) {
+ throw new YAMLException(
+ "No writable property '" + key + "' on class: " + beanType.getName());
+ }
+
+ valueNode.setType(property.getType());
+ final boolean typeDetected =
+ memberDescription != null && memberDescription.setupPropertyType(key, valueNode);
+ if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
+ // only if there is no explicit TypeDescription
+ Class<?>[] arguments = property.getActualTypeArguments();
+ if (arguments != null && arguments.length > 0) {
+ // type safe (generic) collection may contain the
+ // proper class
+ if (valueNode.getNodeId() == NodeId.sequence) {
+ Class<?> t = arguments[0];
+ SequenceNode snode = (SequenceNode) valueNode;
+ snode.setListType(t);
+ } else if (Map.class.isAssignableFrom(valueNode.getType())) {
+ Class<?> keyType = arguments[0];
+ Class<?> valueType = arguments[1];
+ MappingNode mnode = (MappingNode) valueNode;
+ mnode.setTypes(keyType, valueType);
+ mnode.setUseClassConstructor(true);
+ } else if (Collection.class.isAssignableFrom(valueNode.getType())) {
+ Class<?> t = arguments[0];
+ MappingNode mnode = (MappingNode) valueNode;
+ mnode.setOnlyKeyType(t);
+ mnode.setUseClassConstructor(true);
+ }
}
- }
-
- @SuppressWarnings("unchecked")
- public void construct2ndStep(Node node, Object object) {
- if (Map.class.isAssignableFrom(node.getType())) {
- constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
- } else if (Set.class.isAssignableFrom(node.getType())) {
- constructSet2ndStep((MappingNode) node, (Set<Object>) object);
- } else {
- constructJavaBean2ndStep((MappingNode) node, object);
+ }
+
+ Object value =
+ (memberDescription != null) ? newInstance(memberDescription, key, valueNode)
+ : constructObject(valueNode);
+ // Correct when the property expects float but double was
+ // constructed
+ if (property.getType() == Float.TYPE || property.getType() == Float.class) {
+ if (value instanceof Double) {
+ value = ((Double) value).floatValue();
}
+ }
+ // Correct when the property a String but the value is binary
+ if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag())
+ && value instanceof byte[]) {
+ value = new String((byte[]) value);
+ }
+
+ if (memberDescription == null || !memberDescription.setProperty(object, key, value)) {
+ property.set(object, value);
+ }
+ } catch (DuplicateKeyException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ConstructorException(
+ "Cannot create property=" + key + " for JavaBean=" + object, node.getStartMark(),
+ e.getMessage(), valueNode.getStartMark(), e);
}
+ }
+ return object;
+ }
- protected Object createEmptyJavaBean(MappingNode node) {
- try {
- /**
- * Using only default constructor. Everything else will be
- * initialized on 2nd step. If we do here some partial
- * initialization, how do we then track what need to be done on
- * 2nd step? I think it is better to get only object here (to
- * have it as reference for recursion) and do all other thing on
- * 2nd step.
- */
- java.lang.reflect.Constructor<?> c = node.getType().getDeclaredConstructor();
- c.setAccessible(true);
- return c.newInstance();
- } catch (Exception e) {
- throw new YAMLException(e);
- }
- }
+ private Object newInstance(TypeDescription memberDescription, String propertyName, Node node) {
+ Object newInstance = memberDescription.newInstance(propertyName, node);
+ if (newInstance != null) {
+ constructedObjects.put(node, newInstance);
+ return constructObjectNoCheck(node);
+ }
+ return constructObject(node);
+ }
- protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
- flattenMapping(node);
- Class<? extends Object> beanType = node.getType();
- List<NodeTuple> nodeValue = node.getValue();
- for (NodeTuple tuple : nodeValue) {
- ScalarNode keyNode;
- if (tuple.getKeyNode() instanceof ScalarNode) {
- // key must be scalar
- keyNode = (ScalarNode) tuple.getKeyNode();
- } else {
- throw new YAMLException("Keys must be scalars but found: " + tuple.getKeyNode());
- }
- Node valueNode = tuple.getValueNode();
- // keys can only be Strings
- keyNode.setType(String.class);
- String key = (String) constructObject(keyNode);
- try {
- Property property = getProperty(beanType, key);
- valueNode.setType(property.getType());
- TypeDescription memberDescription = typeDefinitions.get(beanType);
- boolean typeDetected = false;
- if (memberDescription != null) {
- switch (valueNode.getNodeId()) {
- case sequence:
- SequenceNode snode = (SequenceNode) valueNode;
- Class<? extends Object> memberType = memberDescription
- .getListPropertyType(key);
- if (memberType != null) {
- snode.setListType(memberType);
- typeDetected = true;
- } else if (property.getType().isArray()) {
- snode.setListType(property.getType().getComponentType());
- typeDetected = true;
- }
- break;
- case mapping:
- MappingNode mnode = (MappingNode) valueNode;
- Class<? extends Object> keyType = memberDescription.getMapKeyType(key);
- if (keyType != null) {
- mnode.setTypes(keyType, memberDescription.getMapValueType(key));
- typeDetected = true;
- }
- break;
- default: // scalar
- }
- }
- if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
- // only if there is no explicit TypeDescription
- Class<?>[] arguments = property.getActualTypeArguments();
- if (arguments != null && arguments.length > 0) {
- // type safe (generic) collection may contain the
- // proper class
- if (valueNode.getNodeId() == NodeId.sequence) {
- Class<?> t = arguments[0];
- SequenceNode snode = (SequenceNode) valueNode;
- snode.setListType(t);
- } else if (valueNode.getTag().equals(Tag.SET)) {
- Class<?> t = arguments[0];
- MappingNode mnode = (MappingNode) valueNode;
- mnode.setOnlyKeyType(t);
- mnode.setUseClassConstructor(true);
- } else if (property.getType().isAssignableFrom(Map.class)) {
- Class<?> ketType = arguments[0];
- Class<?> valueType = arguments[1];
- MappingNode mnode = (MappingNode) valueNode;
- mnode.setTypes(ketType, valueType);
- mnode.setUseClassConstructor(true);
- } else {
- // the type for collection entries cannot be
- // detected
- }
- }
- }
-
- Object value = constructObject(valueNode);
- // Correct when the property expects float but double was
- // constructed
- if (property.getType() == Float.TYPE || property.getType() == Float.class) {
- if (value instanceof Double) {
- value = ((Double) value).floatValue();
- }
- }
- // Correct when the property a String but the value is binary
- if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag()) && value instanceof byte[]) {
- value = new String((byte[])value);
- }
-
- property.set(object, value);
- } catch (Exception e) {
- throw new ConstructorException("Cannot create property=" + key
- + " for JavaBean=" + object, node.getStartMark(), e.getMessage(),
- valueNode.getStartMark(), e);
- }
- }
- return object;
- }
+ protected Property getProperty(Class<? extends Object> type, String name) {
+ return getPropertyUtils().getProperty(type, name);
+ }
+ }
+
+ /**
+ * Construct an instance when the runtime class is not known but a global tag with a class name is
+ * defined. It delegates the construction to the appropriate constructor based on the node kind
+ * (scalar, sequence, mapping)
+ */
+ protected class ConstructYamlObject implements Construct {
+
+ private Construct getConstructor(Node node) {
+ Class<?> cl = getClassForNode(node);
+ node.setType(cl);
+ // call the constructor as if the runtime class is defined
+ Construct constructor = yamlClassConstructors.get(node.getNodeId());
+ return constructor;
+ }
- protected Property getProperty(Class<? extends Object> type, String name)
- throws IntrospectionException {
- return getPropertyUtils().getProperty(type, name);
- }
+ public Object construct(Node node) {
+ try {
+ return getConstructor(node).construct(node);
+ } catch (ConstructorException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ConstructorException(null, null,
+ "Can't construct a java object for " + node.getTag() + "; exception=" + e.getMessage(),
+ node.getStartMark(), e);
+ }
}
- /**
- * Construct an instance when the runtime class is not known but a global
- * tag with a class name is defined. It delegates the construction to the
- * appropriate constructor based on the node kind (scalar, sequence,
- * mapping)
- */
- protected class ConstructYamlObject implements Construct {
-
- private Construct getConstructor(Node node) {
- Class<?> cl = getClassForNode(node);
- node.setType(cl);
- // call the constructor as if the runtime class is defined
- Construct constructor = yamlClassConstructors.get(node.getNodeId());
- return constructor;
+ public void construct2ndStep(Node node, Object object) {
+ try {
+ getConstructor(node).construct2ndStep(node, object);
+ } catch (Exception e) {
+ throw new ConstructorException(null, null,
+ "Can't construct a second step for a java object for " + node.getTag() + "; exception="
+ + e.getMessage(),
+ node.getStartMark(), e);
+ }
+ }
+ }
+
+ /**
+ * Construct scalar instance when the runtime class is known. Recursive structures are not
+ * supported.
+ */
+ protected class ConstructScalar extends AbstractConstruct {
+
+ public Object construct(Node nnode) {
+ ScalarNode node = (ScalarNode) nnode;
+ Class<?> type = node.getType();
+
+ // In case there is TypeDefinition for the 'type'
+ Object instance = newInstance(type, node, false);
+ if (instance != NOT_INSTANTIATED_OBJECT) {
+ return instance;
+ }
+
+ Object result;
+ if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
+ || type == Boolean.class || Date.class.isAssignableFrom(type) || type == Character.class
+ || type == BigInteger.class || type == BigDecimal.class
+ || Enum.class.isAssignableFrom(type) || Tag.BINARY.equals(node.getTag())
+ || Calendar.class.isAssignableFrom(type) || type == UUID.class) {
+ // standard classes created directly
+ result = constructStandardJavaInstance(type, node);
+ } else {
+ // there must be only 1 constructor with 1 argument
+ java.lang.reflect.Constructor<?>[] javaConstructors = type.getDeclaredConstructors();
+ int oneArgCount = 0;
+ java.lang.reflect.Constructor<?> javaConstructor = null;
+ for (java.lang.reflect.Constructor<?> c : javaConstructors) {
+ if (c.getParameterTypes().length == 1) {
+ oneArgCount++;
+ javaConstructor = c;
+ }
}
-
- public Object construct(Node node) {
- Object result = null;
- try {
- result = getConstructor(node).construct(node);
- } catch (ConstructorException e) {
- throw e;
- } catch (Exception e) {
- throw new ConstructorException(null, null, "Can't construct a java object for "
- + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
- }
- return result;
+ Object argument;
+ if (javaConstructor == null) {
+ throw new YAMLException("No single argument constructor found for " + type);
+ } else if (oneArgCount == 1) {
+ argument = constructStandardJavaInstance(javaConstructor.getParameterTypes()[0], node);
+ } else {
+ // TODO it should be possible to use implicit types instead
+ // of forcing String. Resolver must be available here to
+ // obtain the implicit tag. Then we can set the tag and call
+ // callConstructor(node) to create the argument instance.
+ // On the other hand it may be safer to require a custom
+ // constructor to avoid guessing the argument class
+ argument = constructScalar(node);
+ try {
+ javaConstructor = type.getDeclaredConstructor(String.class);
+ } catch (Exception e) {
+ throw new YAMLException("Can't construct a java object for scalar " + node.getTag()
+ + "; No String constructor found. Exception=" + e.getMessage(), e);
+ }
}
-
- public void construct2ndStep(Node node, Object object) {
- try {
- getConstructor(node).construct2ndStep(node, object);
- } catch (Exception e) {
- throw new ConstructorException(null, null,
- "Can't construct a second step for a java object for " + node.getTag()
- + "; exception=" + e.getMessage(), node.getStartMark(), e);
- }
+ try {
+ javaConstructor.setAccessible(true);
+ result = javaConstructor.newInstance(argument);
+ } catch (Exception e) {
+ throw new ConstructorException(null, null, "Can't construct a java object for scalar "
+ + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
}
+ }
+ return result;
}
- /**
- * Construct scalar instance when the runtime class is known. Recursive
- * structures are not supported.
- */
- protected class ConstructScalar extends AbstractConstruct {
- public Object construct(Node nnode) {
- ScalarNode node = (ScalarNode) nnode;
- Class<?> type = node.getType();
- Object result;
- if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
- || type == Boolean.class || Date.class.isAssignableFrom(type)
- || type == Character.class || type == BigInteger.class
- || type == BigDecimal.class || Enum.class.isAssignableFrom(type)
- || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type) || type == UUID.class) {
- // standard classes created directly
- result = constructStandardJavaInstance(type, node);
- } else {
- // there must be only 1 constructor with 1 argument
- java.lang.reflect.Constructor<?>[] javaConstructors = type
- .getDeclaredConstructors();
- int oneArgCount = 0;
- java.lang.reflect.Constructor<?> javaConstructor = null;
- for (java.lang.reflect.Constructor<?> c : javaConstructors) {
- if (c.getParameterTypes().length == 1) {
- oneArgCount++;
- javaConstructor = c;
- }
- }
- Object argument;
- if (javaConstructor == null) {
- throw new YAMLException("No single argument constructor found for " + type);
- } else if (oneArgCount == 1) {
- argument = constructStandardJavaInstance(
- javaConstructor.getParameterTypes()[0], node);
- } else {
- // TODO it should be possible to use implicit types instead
- // of forcing String. Resolver must be available here to
- // obtain the implicit tag. Then we can set the tag and call
- // callConstructor(node) to create the argument instance.
- // On the other hand it may be safer to require a custom
- // constructor to avoid guessing the argument class
- argument = constructScalar(node);
- try {
- javaConstructor = type.getDeclaredConstructor(String.class);
- } catch (Exception e) {
- throw new YAMLException("Can't construct a java object for scalar "
- + node.getTag() + "; No String constructor found. Exception="
- + e.getMessage(), e);
- }
- }
- try {
- javaConstructor.setAccessible(true);
- result = javaConstructor.newInstance(argument);
- } catch (Exception e) {
- throw new ConstructorException(null, null,
- "Can't construct a java object for scalar " + node.getTag()
- + "; exception=" + e.getMessage(), node.getStartMark(), e);
- }
- }
- return result;
+ @SuppressWarnings("unchecked")
+ private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type,
+ ScalarNode node) {
+ Object result;
+ if (type == String.class) {
+ Construct stringConstructor = yamlConstructors.get(Tag.STR);
+ result = stringConstructor.construct(node);
+ } else if (type == Boolean.class || type == Boolean.TYPE) {
+ Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
+ result = boolConstructor.construct(node);
+ } else if (type == Character.class || type == Character.TYPE) {
+ Construct charConstructor = yamlConstructors.get(Tag.STR);
+ String ch = (String) charConstructor.construct(node);
+ if (ch.length() == 0) {
+ result = null;
+ } else if (ch.length() != 1) {
+ throw new YAMLException("Invalid node Character: '" + ch + "'; length: " + ch.length());
+ } else {
+ result = Character.valueOf(ch.charAt(0));
}
-
- @SuppressWarnings("unchecked")
- private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes")
- Class type, ScalarNode node) {
- Object result;
- if (type == String.class) {
- Construct stringConstructor = yamlConstructors.get(Tag.STR);
- result = stringConstructor.construct(node);
- } else if (type == Boolean.class || type == Boolean.TYPE) {
- Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
- result = boolConstructor.construct(node);
- } else if (type == Character.class || type == Character.TYPE) {
- Construct charConstructor = yamlConstructors.get(Tag.STR);
- String ch = (String) charConstructor.construct(node);
- if (ch.length() == 0) {
- result = null;
- } else if (ch.length() != 1) {
- throw new YAMLException("Invalid node Character: '" + ch + "'; length: "
- + ch.length());
- } else {
- result = Character.valueOf(ch.charAt(0));
- }
- } else if (Date.class.isAssignableFrom(type)) {
- Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
- Date date = (Date) dateConstructor.construct(node);
- if (type == Date.class) {
- result = date;
- } else {
- try {
- java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
- result = constr.newInstance(date.getTime());
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- throw new YAMLException("Cannot construct: '" + type + "'");
- }
- }
- } else if (type == Float.class || type == Double.class || type == Float.TYPE
- || type == Double.TYPE || type == BigDecimal.class) {
- if (type == BigDecimal.class) {
- result = new BigDecimal(node.getValue());
- } else {
- Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
- result = doubleConstructor.construct(node);
- if (type == Float.class || type == Float.TYPE) {
- result = new Float((Double) result);
- }
- }
- } else if (type == Byte.class || type == Short.class || type == Integer.class
- || type == Long.class || type == BigInteger.class || type == Byte.TYPE
- || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
- Construct intConstructor = yamlConstructors.get(Tag.INT);
- result = intConstructor.construct(node);
- if (type == Byte.class || type == Byte.TYPE) {
- result = Byte.valueOf(result.toString());
- } else if (type == Short.class || type == Short.TYPE) {
- result = Short.valueOf(result.toString());
- } else if (type == Integer.class || type == Integer.TYPE) {
- result = Integer.parseInt(result.toString());
- } else if (type == Long.class || type == Long.TYPE) {
- result = Long.valueOf(result.toString());
- } else {
- // only BigInteger left
- result = new BigInteger(result.toString());
- }
- } else if (Enum.class.isAssignableFrom(type)) {
- String enumValueName = node.getValue();
- try {
- result = Enum.valueOf(type, enumValueName);
- } catch (Exception ex) {
- throw new YAMLException("Unable to find enum value '" + enumValueName
- + "' for enum class: " + type.getName());
- }
- } else if (Calendar.class.isAssignableFrom(type)) {
- ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
- contr.construct(node);
- result = contr.getCalendar();
- } else if (Number.class.isAssignableFrom(type)) {
- ConstructYamlNumber contr = new ConstructYamlNumber();
- result = contr.construct(node);
- } else if (UUID.class == type) {
- result = UUID.fromString(node.getValue());
- } else {
- if (yamlConstructors.containsKey(node.getTag())) {
- result = yamlConstructors.get(node.getTag()).construct(node);
- } else {
- throw new YAMLException("Unsupported class: " + type);
- }
- }
- return result;
+ } else if (Date.class.isAssignableFrom(type)) {
+ Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
+ Date date = (Date) dateConstructor.construct(node);
+ if (type == Date.class) {
+ result = date;
+ } else {
+ try {
+ java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
+ result = constr.newInstance(date.getTime());
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new YAMLException("Cannot construct: '" + type + "'");
+ }
+ }
+ } else if (type == Float.class || type == Double.class || type == Float.TYPE
+ || type == Double.TYPE || type == BigDecimal.class) {
+ if (type == BigDecimal.class) {
+ result = new BigDecimal(node.getValue());
+ } else {
+ Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
+ result = doubleConstructor.construct(node);
+ if (type == Float.class || type == Float.TYPE) {
+ result = Float.valueOf(((Double) result).floatValue());
+ }
+ }
+ } else if (type == Byte.class || type == Short.class || type == Integer.class
+ || type == Long.class || type == BigInteger.class || type == Byte.TYPE
+ || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
+ Construct intConstructor = yamlConstructors.get(Tag.INT);
+ result = intConstructor.construct(node);
+ if (type == Byte.class || type == Byte.TYPE) {
+ result = Integer.valueOf(result.toString()).byteValue();
+ } else if (type == Short.class || type == Short.TYPE) {
+ result = Integer.valueOf(result.toString()).shortValue();
+ } else if (type == Integer.class || type == Integer.TYPE) {
+ result = Integer.parseInt(result.toString());
+ } else if (type == Long.class || type == Long.TYPE) {
+ result = Long.valueOf(result.toString());
+ } else {
+ // only BigInteger left
+ result = new BigInteger(result.toString());
}
+ } else if (Enum.class.isAssignableFrom(type)) {
+ String enumValueName = node.getValue();
+ try {
+ if (loadingConfig.isEnumCaseSensitive()) {
+ result = Enum.valueOf(type, enumValueName);
+ } else {
+ result = EnumUtils.findEnumInsensitiveCase(type, enumValueName);
+ }
+ } catch (Exception ex) {
+ throw new YAMLException("Unable to find enum value '" + enumValueName
+ + "' for enum class: " + type.getName());
+ }
+ } else if (Calendar.class.isAssignableFrom(type)) {
+ ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
+ contr.construct(node);
+ result = contr.getCalendar();
+ } else if (Number.class.isAssignableFrom(type)) {
+ // since we do not know the exact type we create Float
+ ConstructYamlFloat contr = new ConstructYamlFloat();
+ result = contr.construct(node);
+ } else if (UUID.class == type) {
+ result = UUID.fromString(node.getValue());
+ } else {
+ if (yamlConstructors.containsKey(node.getTag())) {
+ result = yamlConstructors.get(node.getTag()).construct(node);
+ } else {
+ throw new YAMLException("Unsupported class: " + type);
+ }
+ }
+ return result;
}
-
- /**
- * Construct sequence (List, Array, or immutable object) when the runtime
- * class is known.
- */
- protected class ConstructSequence implements Construct {
- @SuppressWarnings("unchecked")
- public Object construct(Node node) {
- SequenceNode snode = (SequenceNode) node;
- if (Set.class.isAssignableFrom(node.getType())) {
- if (node.isTwoStepsConstruction()) {
- throw new YAMLException("Set cannot be recursive.");
- } else {
- return constructSet(snode);
- }
- } else if (Collection.class.isAssignableFrom(node.getType())) {
- if (node.isTwoStepsConstruction()) {
- return createDefaultList(snode.getValue().size());
- } else {
- return constructSequence(snode);
- }
- } else if (node.getType().isArray()) {
- if (node.isTwoStepsConstruction()) {
- return createArray(node.getType(), snode.getValue().size());
- } else {
- return constructArray(snode);
- }
- } else {
- // create immutable object
- List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>(
- snode.getValue().size());
- for (java.lang.reflect.Constructor<?> constructor : node
- .getType().getDeclaredConstructors()) {
- if (snode.getValue()
- .size() == constructor.getParameterTypes().length) {
- possibleConstructors.add(constructor);
- }
- }
- if (!possibleConstructors.isEmpty()) {
- if (possibleConstructors.size() == 1) {
- Object[] argumentList = new Object[snode.getValue().size()];
- java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
- int index = 0;
- for (Node argumentNode : snode.getValue()) {
- Class<?> type = c.getParameterTypes()[index];
- // set runtime classes for arguments
- argumentNode.setType(type);
- argumentList[index++] = constructObject(argumentNode);
- }
-
- try {
- c.setAccessible(true);
- return c.newInstance(argumentList);
- } catch (Exception e) {
- throw new YAMLException(e);
- }
- }
-
- // use BaseConstructor
- List<Object> argumentList = (List<Object>) constructSequence(snode);
- Class<?>[] parameterTypes = new Class[argumentList.size()];
- int index = 0;
- for (Object parameter : argumentList) {
- parameterTypes[index] = parameter.getClass();
- index++;
- }
-
- for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
- Class<?>[] argTypes = c.getParameterTypes();
- boolean foundConstructor = true;
- for (int i = 0; i < argTypes.length; i++) {
- if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
- foundConstructor = false;
- break;
- }
- }
- if (foundConstructor) {
- try {
- c.setAccessible(true);
- return c.newInstance(argumentList.toArray());
- } catch (Exception e) {
- throw new YAMLException(e);
- }
- }
- }
- }
- throw new YAMLException("No suitable constructor with "
- + String.valueOf(snode.getValue().size()) + " arguments found for "
- + node.getType());
-
- }
+ }
+
+ /**
+ * Construct sequence (List, Array, or immutable object) when the runtime class is known.
+ */
+ protected class ConstructSequence implements Construct {
+
+ @SuppressWarnings("unchecked")
+ public Object construct(Node node) {
+ SequenceNode snode = (SequenceNode) node;
+ if (Set.class.isAssignableFrom(node.getType())) {
+ if (node.isTwoStepsConstruction()) {
+ throw new YAMLException("Set cannot be recursive.");
+ } else {
+ return constructSet(snode);
}
-
- private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
- if (!clazz.isPrimitive()) {
- return clazz;
- }
- if (clazz == Integer.TYPE) {
- return Integer.class;
- }
- if (clazz == Float.TYPE) {
- return Float.class;
- }
- if (clazz == Double.TYPE) {
- return Double.class;
- }
- if (clazz == Boolean.TYPE) {
- return Boolean.class;
- }
- if (clazz == Long.TYPE) {
- return Long.class;
+ } else if (Collection.class.isAssignableFrom(node.getType())) {
+ if (node.isTwoStepsConstruction()) {
+ return newList(snode);
+ } else {
+ return constructSequence(snode);
+ }
+ } else if (node.getType().isArray()) {
+ if (node.isTwoStepsConstruction()) {
+ return createArray(node.getType(), snode.getValue().size());
+ } else {
+ return constructArray(snode);
+ }
+ } else {
+ // create immutable object
+ List<java.lang.reflect.Constructor<?>> possibleConstructors =
+ new ArrayList<java.lang.reflect.Constructor<?>>(snode.getValue().size());
+ for (java.lang.reflect.Constructor<?> constructor : node.getType()
+ .getDeclaredConstructors()) {
+ if (snode.getValue().size() == constructor.getParameterTypes().length) {
+ possibleConstructors.add(constructor);
+ }
+ }
+ if (!possibleConstructors.isEmpty()) {
+ if (possibleConstructors.size() == 1) {
+ Object[] argumentList = new Object[snode.getValue().size()];
+ java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
+ int index = 0;
+ for (Node argumentNode : snode.getValue()) {
+ Class<?> type = c.getParameterTypes()[index];
+ // set runtime classes for arguments
+ argumentNode.setType(type);
+ argumentList[index++] = constructObject(argumentNode);
}
- if (clazz == Character.TYPE) {
- return Character.class;
+
+ try {
+ c.setAccessible(true);
+ return c.newInstance(argumentList);
+ } catch (Exception e) {
+ throw new YAMLException(e);
}
- if (clazz == Short.TYPE) {
- return Short.class;
+ }
+
+ // use BaseConstructor
+ List<Object> argumentList = (List<Object>) constructSequence(snode);
+ Class<?>[] parameterTypes = new Class[argumentList.size()];
+ int index = 0;
+ for (Object parameter : argumentList) {
+ parameterTypes[index] = parameter.getClass();
+ index++;
+ }
+
+ for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
+ Class<?>[] argTypes = c.getParameterTypes();
+ boolean foundConstructor = true;
+ for (int i = 0; i < argTypes.length; i++) {
+ if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
+ foundConstructor = false;
+ break;
+ }
}
- if (clazz == Byte.TYPE) {
- return Byte.class;
+ if (foundConstructor) {
+ try {
+ c.setAccessible(true);
+ return c.newInstance(argumentList.toArray());
+ } catch (Exception e) {
+ throw new YAMLException(e);
+ }
}
- throw new YAMLException("Unexpected primitive " + clazz);
+ }
}
+ throw new YAMLException("No suitable constructor with " + snode.getValue().size()
+ + " arguments found for " + node.getType());
- @SuppressWarnings("unchecked")
- public void construct2ndStep(Node node, Object object) {
- SequenceNode snode = (SequenceNode) node;
- if (List.class.isAssignableFrom(node.getType())) {
- List<Object> list = (List<Object>) object;
- constructSequenceStep2(snode, list);
- } else if (node.getType().isArray()) {
- constructArrayStep2(snode, object);
- } else {
- throw new YAMLException("Immutable objects cannot be recursive.");
- }
- }
+ }
}
- protected Class<?> getClassForNode(Node node) {
- Class<? extends Object> classForTag = typeTags.get(node.getTag());
- if (classForTag == null) {
- String name = node.getTag().getClassName();
- Class<?> cl;
- try {
- cl = getClassForName(name);
- } catch (ClassNotFoundException e) {
- throw new YAMLException("Class not found: " + name);
- }
- typeTags.put(node.getTag(), cl);
- return cl;
- } else {
- return classForTag;
- }
+ private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
+ if (!clazz.isPrimitive()) {
+ return clazz;
+ }
+ if (clazz == Integer.TYPE) {
+ return Integer.class;
+ }
+ if (clazz == Float.TYPE) {
+ return Float.class;
+ }
+ if (clazz == Double.TYPE) {
+ return Double.class;
+ }
+ if (clazz == Boolean.TYPE) {
+ return Boolean.class;
+ }
+ if (clazz == Long.TYPE) {
+ return Long.class;
+ }
+ if (clazz == Character.TYPE) {
+ return Character.class;
+ }
+ if (clazz == Short.TYPE) {
+ return Short.class;
+ }
+ if (clazz == Byte.TYPE) {
+ return Byte.class;
+ }
+ throw new YAMLException("Unexpected primitive " + clazz);
}
- protected Class<?> getClassForName(String name) throws ClassNotFoundException {
- try {
- return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException e) {
- return Class.forName(name);
- }
+ @SuppressWarnings("unchecked")
+ public void construct2ndStep(Node node, Object object) {
+ SequenceNode snode = (SequenceNode) node;
+ if (List.class.isAssignableFrom(node.getType())) {
+ List<Object> list = (List<Object>) object;
+ constructSequenceStep2(snode, list);
+ } else if (node.getType().isArray()) {
+ constructArrayStep2(snode, object);
+ } else {
+ throw new YAMLException("Immutable objects cannot be recursive.");
+ }
+ }
+ }
+
+ protected Class<?> getClassForNode(Node node) {
+ Class<? extends Object> classForTag = typeTags.get(node.getTag());
+ if (classForTag == null) {
+ String name = node.getTag().getClassName();
+ Class<?> cl;
+ try {
+ cl = getClassForName(name);
+ } catch (ClassNotFoundException e) {
+ throw new YAMLException("Class not found: " + name);
+ }
+ typeTags.put(node.getTag(), cl);
+ return cl;
+ } else {
+ return classForTag;
+ }
+ }
+
+ protected Class<?> getClassForName(String name) throws ClassNotFoundException {
+ try {
+ return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ return Class.forName(name);
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java b/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java
index 336e3bae..13a672f4 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
@@ -19,15 +17,16 @@ import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.MarkedYAMLException;
public class ConstructorException extends MarkedYAMLException {
- private static final long serialVersionUID = -8816339931365239910L;
- protected ConstructorException(String context, Mark contextMark, String problem,
- Mark problemMark, Throwable cause) {
- super(context, contextMark, problem, problemMark, cause);
- }
+ private static final long serialVersionUID = -8816339931365239910L;
- protected ConstructorException(String context, Mark contextMark, String problem,
- Mark problemMark) {
- this(context, contextMark, problem, problemMark, null);
- }
+ protected ConstructorException(String context, Mark contextMark, String problem, Mark problemMark,
+ Throwable cause) {
+ super(context, contextMark, problem, problemMark, cause);
+ }
+
+ protected ConstructorException(String context, Mark contextMark, String problem,
+ Mark problemMark) {
+ this(context, contextMark, problem, problemMark, null);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java
index edb163da..8773e3ee 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
@@ -19,22 +17,23 @@ package org.yaml.snakeyaml.constructor;
* Construct instances with a custom Class Loader.
*/
public class CustomClassLoaderConstructor extends Constructor {
- private ClassLoader loader = CustomClassLoaderConstructor.class.getClassLoader();
- public CustomClassLoaderConstructor(ClassLoader cLoader) {
- this(Object.class, cLoader);
- }
+ private ClassLoader loader = CustomClassLoaderConstructor.class.getClassLoader();
- public CustomClassLoaderConstructor(Class<? extends Object> theRoot, ClassLoader theLoader) {
- super(theRoot);
- if (theLoader == null) {
- throw new NullPointerException("Loader must be provided.");
- }
- this.loader = theLoader;
- }
+ public CustomClassLoaderConstructor(ClassLoader cLoader) {
+ this(Object.class, cLoader);
+ }
- @Override
- protected Class<?> getClassForName(String name) throws ClassNotFoundException {
- return Class.forName(name, true, loader);
+ public CustomClassLoaderConstructor(Class<? extends Object> theRoot, ClassLoader theLoader) {
+ super(theRoot);
+ if (theLoader == null) {
+ throw new NullPointerException("Loader must be provided.");
}
+ this.loader = theLoader;
+ }
+
+ @Override
+ protected Class<?> getClassForName(String name) throws ClassNotFoundException {
+ return Class.forName(name, true, loader);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java b/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java
new file mode 100644
index 00000000..3a992104
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.constructor;
+
+import org.yaml.snakeyaml.error.Mark;
+
+public class DuplicateKeyException extends ConstructorException {
+
+ protected DuplicateKeyException(Mark contextMark, Object key, Mark problemMark) {
+ super("while constructing a mapping", contextMark, "found duplicate key " + key, problemMark);
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java
index b5572ea0..b34011c5 100644
--- a/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java
+++ b/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java
@@ -1,26 +1,21 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.constructor;
import java.math.BigInteger;
-import java.text.NumberFormat;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -28,9 +23,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
+import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import org.yaml.snakeyaml.nodes.MappingNode;
@@ -46,465 +42,578 @@ import org.yaml.snakeyaml.nodes.Tag;
*/
public class SafeConstructor extends BaseConstructor {
- public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
-
- public SafeConstructor() {
- this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
- this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
- this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
- this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
- this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
- this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
- this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
- this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
- this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
- this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
- this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
- this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
- this.yamlConstructors.put(null, undefinedConstructor);
- this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
- this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
- this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
+ public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
+
+ public SafeConstructor() {
+ this(new LoaderOptions());
+ }
+
+ public SafeConstructor(LoaderOptions loadingConfig) {
+ super(loadingConfig);
+ this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
+ this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
+ this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
+ this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
+ this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
+ this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
+ this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
+ this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
+ this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
+ this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
+ this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
+ this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
+ this.yamlConstructors.put(null, undefinedConstructor);
+ this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
+ this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
+ this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
+ }
+
+ protected void flattenMapping(MappingNode node) {
+ flattenMapping(node, false);
+ }
+
+ protected void flattenMapping(MappingNode node, boolean forceStringKeys) {
+ // perform merging only on nodes containing merge node(s)
+ processDuplicateKeys(node, forceStringKeys);
+ if (node.isMerged()) {
+ node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
+ new ArrayList<NodeTuple>(), forceStringKeys));
}
+ }
+
+ protected void processDuplicateKeys(MappingNode node) {
+ processDuplicateKeys(node, false);
+ }
+
+ protected void processDuplicateKeys(MappingNode node, boolean forceStringKeys) {
+ List<NodeTuple> nodeValue = node.getValue();
+ Map<Object, Integer> keys = new HashMap<Object, Integer>(nodeValue.size());
+ TreeSet<Integer> toRemove = new TreeSet<Integer>();
+ int i = 0;
+ for (NodeTuple tuple : nodeValue) {
+ Node keyNode = tuple.getKeyNode();
+ if (!keyNode.getTag().equals(Tag.MERGE)) {
+ if (forceStringKeys) {
+ if (keyNode instanceof ScalarNode) {
+ keyNode.setType(String.class);
+ keyNode.setTag(Tag.STR);
+ } else {
+ throw new YAMLException("Keys must be scalars but found: " + keyNode);
+ }
+ }
+ Object key = constructObject(keyNode);
+ if (key != null && !forceStringKeys) {
+ if (keyNode.isTwoStepsConstruction()) {
+ if (!loadingConfig.getAllowRecursiveKeys()) {
+ throw new YAMLException(
+ "Recursive key for mapping is detected but it is not configured to be allowed.");
+ } else {
+ try {
+ key.hashCode();// check circular dependencies
+ } catch (Exception e) {
+ throw new ConstructorException("while constructing a mapping", node.getStartMark(),
+ "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
+ }
+ }
+ }
+ }
- protected void flattenMapping(MappingNode node) {
- // perform merging only on nodes containing merge node(s)
- if (node.isMerged()) {
- node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
- new ArrayList<NodeTuple>()));
+ Integer prevIndex = keys.put(key, i);
+ if (prevIndex != null) {
+ if (!isAllowDuplicateKeys()) {
+ throw new DuplicateKeyException(node.getStartMark(), key,
+ tuple.getKeyNode().getStartMark());
+ }
+ toRemove.add(prevIndex);
}
+ }
+ i = i + 1;
}
- /**
- * Does merge for supplied mapping node.
- *
- * @param node
- * where to merge
- * @param isPreffered
- * true if keys of node should take precedence over others...
- * @param key2index
- * maps already merged keys to index from values
- * @param values
- * collects merged NodeTuple
- * @return list of the merged NodeTuple (to be set as value for the
- * MappingNode)
- */
- private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
- Map<Object, Integer> key2index, List<NodeTuple> values) {
- List<NodeTuple> nodeValue = node.getValue();
- // reversed for http://code.google.com/p/snakeyaml/issues/detail?id=139
- Collections.reverse(nodeValue);
- for (Iterator<NodeTuple> iter = nodeValue.iterator(); iter.hasNext();) {
- final NodeTuple nodeTuple = iter.next();
- final Node keyNode = nodeTuple.getKeyNode();
- final Node valueNode = nodeTuple.getValueNode();
- if (keyNode.getTag().equals(Tag.MERGE)) {
- iter.remove();
- switch (valueNode.getNodeId()) {
- case mapping:
- MappingNode mn = (MappingNode) valueNode;
- mergeNode(mn, false, key2index, values);
- break;
- case sequence:
- SequenceNode sn = (SequenceNode) valueNode;
- List<Node> vals = sn.getValue();
- for (Node subnode : vals) {
- if (!(subnode instanceof MappingNode)) {
- throw new ConstructorException("while constructing a mapping",
- node.getStartMark(),
- "expected a mapping for merging, but found "
- + subnode.getNodeId(), subnode.getStartMark());
- }
- MappingNode mnode = (MappingNode) subnode;
- mergeNode(mnode, false, key2index, values);
- }
- break;
- default:
- throw new ConstructorException("while constructing a mapping",
- node.getStartMark(),
- "expected a mapping or list of mappings for merging, but found "
- + valueNode.getNodeId(), valueNode.getStartMark());
- }
- } else {
- // we need to construct keys to avoid duplications
- Object key = constructObject(keyNode);
- if (!key2index.containsKey(key)) { // 1st time merging key
- values.add(nodeTuple);
- // keep track where tuple for the key is
- key2index.put(key, values.size() - 1);
- } else if (isPreffered) { // there is value for the key, but we
- // need to override it
- // change value for the key using saved position
- values.set(key2index.get(key), nodeTuple);
- }
+ Iterator<Integer> indices2remove = toRemove.descendingIterator();
+ while (indices2remove.hasNext()) {
+ nodeValue.remove(indices2remove.next().intValue());
+ }
+ }
+
+ /**
+ * Does merge for supplied mapping node.
+ *
+ * @param node where to merge
+ * @param isPreffered true if keys of node should take precedence over others...
+ * @param key2index maps already merged keys to index from values
+ * @param values collects merged NodeTuple
+ * @return list of the merged NodeTuple (to be set as value for the MappingNode)
+ */
+ private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
+ Map<Object, Integer> key2index, List<NodeTuple> values, boolean forceStringKeys) {
+ Iterator<NodeTuple> iter = node.getValue().iterator();
+ while (iter.hasNext()) {
+ final NodeTuple nodeTuple = iter.next();
+ final Node keyNode = nodeTuple.getKeyNode();
+ final Node valueNode = nodeTuple.getValueNode();
+ if (keyNode.getTag().equals(Tag.MERGE)) {
+ iter.remove();
+ switch (valueNode.getNodeId()) {
+ case mapping:
+ MappingNode mn = (MappingNode) valueNode;
+ mergeNode(mn, false, key2index, values, forceStringKeys);
+ break;
+ case sequence:
+ SequenceNode sn = (SequenceNode) valueNode;
+ List<Node> vals = sn.getValue();
+ for (Node subnode : vals) {
+ if (!(subnode instanceof MappingNode)) {
+ throw new ConstructorException("while constructing a mapping", node.getStartMark(),
+ "expected a mapping for merging, but found " + subnode.getNodeId(),
+ subnode.getStartMark());
+ }
+ MappingNode mnode = (MappingNode) subnode;
+ mergeNode(mnode, false, key2index, values, forceStringKeys);
}
+ break;
+ default:
+ throw new ConstructorException("while constructing a mapping", node.getStartMark(),
+ "expected a mapping or list of mappings for merging, but found "
+ + valueNode.getNodeId(),
+ valueNode.getStartMark());
+ }
+ } else {
+ // we need to construct keys to avoid duplications
+ if (forceStringKeys) {
+ if (keyNode instanceof ScalarNode) {
+ keyNode.setType(String.class);
+ keyNode.setTag(Tag.STR);
+ } else {
+ throw new YAMLException("Keys must be scalars but found: " + keyNode);
+ }
+ }
+ Object key = constructObject(keyNode);
+ if (!key2index.containsKey(key)) { // 1st time merging key
+ values.add(nodeTuple);
+ // keep track where tuple for the key is
+ key2index.put(key, values.size() - 1);
+ } else if (isPreffered) { // there is value for the key, but we
+ // need to override it
+ // change value for the key using saved position
+ values.set(key2index.get(key), nodeTuple);
}
- return values;
+ }
}
+ return values;
+ }
+
+ @Override
+ protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
+ flattenMapping(node);
+ super.constructMapping2ndStep(node, mapping);
+ }
+
+ @Override
+ protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
+ flattenMapping(node);
+ super.constructSet2ndStep(node, set);
+ }
+
+ public class ConstructYamlNull extends AbstractConstruct {
- protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
- flattenMapping(node);
- super.constructMapping2ndStep(node, mapping);
+ @Override
+ public Object construct(Node node) {
+ if (node != null) {
+ constructScalar((ScalarNode) node);
+ }
+ return null;
}
+ }
+
+ private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
+
+ static {
+ BOOL_VALUES.put("yes", Boolean.TRUE);
+ BOOL_VALUES.put("no", Boolean.FALSE);
+ BOOL_VALUES.put("true", Boolean.TRUE);
+ BOOL_VALUES.put("false", Boolean.FALSE);
+ BOOL_VALUES.put("on", Boolean.TRUE);
+ BOOL_VALUES.put("off", Boolean.FALSE);
+ }
+
+ public class ConstructYamlBool extends AbstractConstruct {
@Override
- protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
- flattenMapping(node);
- super.constructSet2ndStep(node, set);
+ public Object construct(Node node) {
+ String val = constructScalar((ScalarNode) node);
+ return BOOL_VALUES.get(val.toLowerCase());
}
+ }
+
+ public class ConstructYamlInt extends AbstractConstruct {
- public class ConstructYamlNull extends AbstractConstruct {
- public Object construct(Node node) {
- constructScalar((ScalarNode) node);
- return null;
+ @Override
+ public Object construct(Node node) {
+ String value = constructScalar((ScalarNode) node).replaceAll("_", "");
+ if (value.isEmpty()) {
+ throw new ConstructorException("while constructing an int", node.getStartMark(),
+ "found empty value", node.getStartMark());
+ }
+ int sign = +1;
+ char first = value.charAt(0);
+ if (first == '-') {
+ sign = -1;
+ value = value.substring(1);
+ } else if (first == '+') {
+ value = value.substring(1);
+ }
+ int base = 10;
+ if ("0".equals(value)) {
+ return Integer.valueOf(0);
+ } else if (value.startsWith("0b")) {
+ value = value.substring(2);
+ base = 2;
+ } else if (value.startsWith("0x")) {
+ value = value.substring(2);
+ base = 16;
+ } else if (value.startsWith("0")) {
+ value = value.substring(1);
+ base = 8;
+ } else if (value.indexOf(':') != -1) {
+ String[] digits = value.split(":");
+ int bes = 1;
+ int val = 0;
+ for (int i = 0, j = digits.length; i < j; i++) {
+ val += Long.parseLong(digits[j - i - 1]) * bes;
+ bes *= 60;
}
+ return createNumber(sign, String.valueOf(val), 10);
+ } else {
+ return createNumber(sign, value, 10);
+ }
+ return createNumber(sign, value, base);
}
+ }
- private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
- static {
- BOOL_VALUES.put("yes", Boolean.TRUE);
- BOOL_VALUES.put("no", Boolean.FALSE);
- BOOL_VALUES.put("true", Boolean.TRUE);
- BOOL_VALUES.put("false", Boolean.FALSE);
- BOOL_VALUES.put("on", Boolean.TRUE);
- BOOL_VALUES.put("off", Boolean.FALSE);
+ private static final int[][] RADIX_MAX = new int[17][2];
+
+ static {
+ int[] radixList = new int[] {2, 8, 10, 16};
+ for (int radix : radixList) {
+ RADIX_MAX[radix] =
+ new int[] {maxLen(Integer.MAX_VALUE, radix), maxLen(Long.MAX_VALUE, radix)};
}
+ }
+
+ private static int maxLen(final int max, final int radix) {
+ return Integer.toString(max, radix).length();
+ }
+
+ private static int maxLen(final long max, final int radix) {
+ return Long.toString(max, radix).length();
+ }
- public class ConstructYamlBool extends AbstractConstruct {
- public Object construct(Node node) {
- String val = (String) constructScalar((ScalarNode) node);
- return BOOL_VALUES.get(val.toLowerCase());
+ private Number createNumber(int sign, String number, int radix) {
+ final int len = number != null ? number.length() : 0;
+ if (sign < 0) {
+ number = "-" + number;
+ }
+ final int[] maxArr = radix < RADIX_MAX.length ? RADIX_MAX[radix] : null;
+ if (maxArr != null) {
+ final boolean gtInt = len > maxArr[0];
+ if (gtInt) {
+ if (len > maxArr[1]) {
+ return new BigInteger(number, radix);
}
+ return createLongOrBigInteger(number, radix);
+ }
+ }
+ Number result;
+ try {
+ result = Integer.valueOf(number, radix);
+ } catch (NumberFormatException e) {
+ result = createLongOrBigInteger(number, radix);
+ }
+ return result;
+ }
+
+ protected static Number createLongOrBigInteger(final String number, final int radix) {
+ try {
+ return Long.valueOf(number, radix);
+ } catch (NumberFormatException e1) {
+ return new BigInteger(number, radix);
}
+ }
- public class ConstructYamlInt extends AbstractConstruct {
- public Object construct(Node node) {
- String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
- int sign = +1;
- char first = value.charAt(0);
- if (first == '-') {
- sign = -1;
- value = value.substring(1);
- } else if (first == '+') {
- value = value.substring(1);
- }
- int base = 10;
- if ("0".equals(value)) {
- return Integer.valueOf(0);
- } else if (value.startsWith("0b")) {
- value = value.substring(2);
- base = 2;
- } else if (value.startsWith("0x")) {
- value = value.substring(2);
- base = 16;
- } else if (value.startsWith("0")) {
- value = value.substring(1);
- base = 8;
- } else if (value.indexOf(':') != -1) {
- String[] digits = value.split(":");
- int bes = 1;
- int val = 0;
- for (int i = 0, j = digits.length; i < j; i++) {
- val += Long.parseLong(digits[j - i - 1]) * bes;
- bes *= 60;
- }
- return createNumber(sign, String.valueOf(val), 10);
- } else {
- return createNumber(sign, value, 10);
- }
- return createNumber(sign, value, base);
+ public class ConstructYamlFloat extends AbstractConstruct {
+
+ @Override
+ public Object construct(Node node) {
+ String value = constructScalar((ScalarNode) node).replaceAll("_", "");
+ if (value.isEmpty()) {
+ throw new ConstructorException("while constructing a float", node.getStartMark(),
+ "found empty value", node.getStartMark());
+ }
+ int sign = +1;
+ char first = value.charAt(0);
+ if (first == '-') {
+ sign = -1;
+ value = value.substring(1);
+ } else if (first == '+') {
+ value = value.substring(1);
+ }
+ String valLower = value.toLowerCase();
+ if (".inf".equals(valLower)) {
+ return Double.valueOf(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
+ } else if (".nan".equals(valLower)) {
+ return Double.valueOf(Double.NaN);
+ } else if (value.indexOf(':') != -1) {
+ String[] digits = value.split(":");
+ int bes = 1;
+ double val = 0.0;
+ for (int i = 0, j = digits.length; i < j; i++) {
+ val += Double.parseDouble(digits[j - i - 1]) * bes;
+ bes *= 60;
}
+ return Double.valueOf(sign * val);
+ } else {
+ Double d = Double.valueOf(value);
+ return Double.valueOf(d.doubleValue() * sign);
+ }
}
+ }
- private Number createNumber(int sign, String number, int radix) {
- Number result;
- if (sign < 0) {
- number = "-" + number;
- }
- try {
- result = Integer.valueOf(number, radix);
- } catch (NumberFormatException e) {
- try {
- result = Long.valueOf(number, radix);
- } catch (NumberFormatException e1) {
- result = new BigInteger(number, radix);
- }
- }
- return result;
+ public class ConstructYamlBinary extends AbstractConstruct {
+
+ @Override
+ public Object construct(Node node) {
+ // Ignore white spaces for base64 encoded scalar
+ String noWhiteSpaces = constructScalar((ScalarNode) node).replaceAll("\\s", "");
+ byte[] decoded = Base64Coder.decode(noWhiteSpaces.toCharArray());
+ return decoded;
}
+ }
- public class ConstructYamlFloat extends AbstractConstruct {
- public Object construct(Node node) {
- String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
- int sign = +1;
- char first = value.charAt(0);
- if (first == '-') {
- sign = -1;
- value = value.substring(1);
- } else if (first == '+') {
- value = value.substring(1);
- }
- String valLower = value.toLowerCase();
- if (".inf".equals(valLower)) {
- return new Double(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
- } else if (".nan".equals(valLower)) {
- return new Double(Double.NaN);
- } else if (value.indexOf(':') != -1) {
- String[] digits = value.split(":");
- int bes = 1;
- double val = 0.0;
- for (int i = 0, j = digits.length; i < j; i++) {
- val += Double.parseDouble(digits[j - i - 1]) * bes;
- bes *= 60;
- }
- return new Double(sign * val);
- } else {
- Double d = Double.valueOf(value);
- return new Double(d.doubleValue() * sign);
- }
- }
+ private final static Pattern TIMESTAMP_REGEXP = Pattern.compile(
+ "^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$");
+ private final static Pattern YMD_REGEXP =
+ Pattern.compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
+
+ public static class ConstructYamlTimestamp extends AbstractConstruct {
+
+ private Calendar calendar;
+
+ public Calendar getCalendar() {
+ return calendar;
}
- public class ConstructYamlBinary extends AbstractConstruct {
- public Object construct(Node node) {
- byte[] decoded = Base64Coder.decode(constructScalar((ScalarNode) node).toString()
- .toCharArray());
- return decoded;
+ @Override
+ public Object construct(Node node) {
+ ScalarNode scalar = (ScalarNode) node;
+ String nodeValue = scalar.getValue();
+ Matcher match = YMD_REGEXP.matcher(nodeValue);
+ if (match.matches()) {
+ String year_s = match.group(1);
+ String month_s = match.group(2);
+ String day_s = match.group(3);
+ calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.clear();
+ calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
+ // Java's months are zero-based...
+ calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
+ calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
+ return calendar.getTime();
+ } else {
+ match = TIMESTAMP_REGEXP.matcher(nodeValue);
+ if (!match.matches()) {
+ throw new YAMLException("Unexpected timestamp: " + nodeValue);
}
+ String year_s = match.group(1);
+ String month_s = match.group(2);
+ String day_s = match.group(3);
+ String hour_s = match.group(4);
+ String min_s = match.group(5);
+ // seconds and milliseconds
+ String seconds = match.group(6);
+ String millis = match.group(7);
+ if (millis != null) {
+ seconds = seconds + "." + millis;
+ }
+ double fractions = Double.parseDouble(seconds);
+ int sec_s = (int) Math.round(Math.floor(fractions));
+ int usec = (int) Math.round((fractions - sec_s) * 1000);
+ // timezone
+ String timezoneh_s = match.group(8);
+ String timezonem_s = match.group(9);
+ TimeZone timeZone;
+ if (timezoneh_s != null) {
+ String time = timezonem_s != null ? ":" + timezonem_s : "00";
+ timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
+ } else {
+ // no time zone provided
+ timeZone = TimeZone.getTimeZone("UTC");
+ }
+ calendar = Calendar.getInstance(timeZone);
+ calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
+ // Java's months are zero-based...
+ calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
+ calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
+ calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
+ calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
+ calendar.set(Calendar.SECOND, sec_s);
+ calendar.set(Calendar.MILLISECOND, usec);
+ return calendar.getTime();
+ }
}
+ }
- public class ConstructYamlNumber extends AbstractConstruct {
-
- private final NumberFormat nf = NumberFormat.getInstance();
-
- public Object construct(Node node) {
- ScalarNode scalar = (ScalarNode) node;
- try {
- return nf.parse(scalar.getValue());
- } catch (ParseException e) {
- String lowerCaseValue = scalar.getValue().toLowerCase();
- if (lowerCaseValue.contains("inf") || lowerCaseValue.contains("nan")) {
- /*
- * Non-finites such as (+/-)infinity and NaN are not
- * parseable by NumberFormat when these `Double` values are
- * dumped by snakeyaml. Delegate to the `Tag.FLOAT`
- * constructor when for this expected failure cause.
- */
- return (Number) yamlConstructors.get(Tag.FLOAT).construct(node);
- } else {
- throw new IllegalArgumentException("Unable to parse as Number: "
- + scalar.getValue());
- }
- }
+ public class ConstructYamlOmap extends AbstractConstruct {
+
+ @Override
+ public Object construct(Node node) {
+ // Note: we do not check for duplicate keys, because it's too
+ // CPU-expensive.
+ Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
+ if (!(node instanceof SequenceNode)) {
+ throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
+ "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
+ }
+ SequenceNode snode = (SequenceNode) node;
+ for (Node subnode : snode.getValue()) {
+ if (!(subnode instanceof MappingNode)) {
+ throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
+ "expected a mapping of length 1, but found " + subnode.getNodeId(),
+ subnode.getStartMark());
}
+ MappingNode mnode = (MappingNode) subnode;
+ if (mnode.getValue().size() != 1) {
+ throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
+ "expected a single mapping item, but found " + mnode.getValue().size() + " items",
+ mnode.getStartMark());
+ }
+ Node keyNode = mnode.getValue().get(0).getKeyNode();
+ Node valueNode = mnode.getValue().get(0).getValueNode();
+ Object key = constructObject(keyNode);
+ Object value = constructObject(valueNode);
+ omap.put(key, value);
+ }
+ return omap;
}
+ }
- private final static Pattern TIMESTAMP_REGEXP = Pattern
- .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$");
- private final static Pattern YMD_REGEXP = Pattern
- .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
-
- public static class ConstructYamlTimestamp extends AbstractConstruct {
- private Calendar calendar;
+ public class ConstructYamlPairs extends AbstractConstruct {
- public Calendar getCalendar() {
- return calendar;
+ @Override
+ public Object construct(Node node) {
+ // Note: we do not check for duplicate keys, because it's too
+ // CPU-expensive.
+ if (!(node instanceof SequenceNode)) {
+ throw new ConstructorException("while constructing pairs", node.getStartMark(),
+ "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
+ }
+ SequenceNode snode = (SequenceNode) node;
+ List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
+ for (Node subnode : snode.getValue()) {
+ if (!(subnode instanceof MappingNode)) {
+ throw new ConstructorException("while constructingpairs", node.getStartMark(),
+ "expected a mapping of length 1, but found " + subnode.getNodeId(),
+ subnode.getStartMark());
}
-
- public Object construct(Node node) {
- ScalarNode scalar = (ScalarNode) node;
- String nodeValue = scalar.getValue();
- Matcher match = YMD_REGEXP.matcher(nodeValue);
- if (match.matches()) {
- String year_s = match.group(1);
- String month_s = match.group(2);
- String day_s = match.group(3);
- calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
- calendar.clear();
- calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
- // Java's months are zero-based...
- calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
- calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
- return calendar.getTime();
- } else {
- match = TIMESTAMP_REGEXP.matcher(nodeValue);
- if (!match.matches()) {
- throw new YAMLException("Unexpected timestamp: " + nodeValue);
- }
- String year_s = match.group(1);
- String month_s = match.group(2);
- String day_s = match.group(3);
- String hour_s = match.group(4);
- String min_s = match.group(5);
- // seconds and milliseconds
- String seconds = match.group(6);
- String millis = match.group(7);
- if (millis != null) {
- seconds = seconds + "." + millis;
- }
- double fractions = Double.parseDouble(seconds);
- int sec_s = (int) Math.round(Math.floor(fractions));
- int usec = (int) Math.round((fractions - sec_s) * 1000);
- // timezone
- String timezoneh_s = match.group(8);
- String timezonem_s = match.group(9);
- TimeZone timeZone;
- if (timezoneh_s != null) {
- String time = timezonem_s != null ? ":" + timezonem_s : "00";
- timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
- } else {
- // no time zone provided
- timeZone = TimeZone.getTimeZone("UTC");
- }
- calendar = Calendar.getInstance(timeZone);
- calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
- // Java's months are zero-based...
- calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
- calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
- calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
- calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
- calendar.set(Calendar.SECOND, sec_s);
- calendar.set(Calendar.MILLISECOND, usec);
- return calendar.getTime();
- }
+ MappingNode mnode = (MappingNode) subnode;
+ if (mnode.getValue().size() != 1) {
+ throw new ConstructorException("while constructing pairs", node.getStartMark(),
+ "expected a single mapping item, but found " + mnode.getValue().size() + " items",
+ mnode.getStartMark());
}
+ Node keyNode = mnode.getValue().get(0).getKeyNode();
+ Node valueNode = mnode.getValue().get(0).getValueNode();
+ Object key = constructObject(keyNode);
+ Object value = constructObject(valueNode);
+ pairs.add(new Object[] {key, value});
+ }
+ return pairs;
}
+ }
- public class ConstructYamlOmap extends AbstractConstruct {
- public Object construct(Node node) {
- // Note: we do not check for duplicate keys, because it's too
- // CPU-expensive.
- Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
- if (!(node instanceof SequenceNode)) {
- throw new ConstructorException("while constructing an ordered map",
- node.getStartMark(), "expected a sequence, but found " + node.getNodeId(),
- node.getStartMark());
- }
- SequenceNode snode = (SequenceNode) node;
- for (Node subnode : snode.getValue()) {
- if (!(subnode instanceof MappingNode)) {
- throw new ConstructorException("while constructing an ordered map",
- node.getStartMark(), "expected a mapping of length 1, but found "
- + subnode.getNodeId(), subnode.getStartMark());
- }
- MappingNode mnode = (MappingNode) subnode;
- if (mnode.getValue().size() != 1) {
- throw new ConstructorException("while constructing an ordered map",
- node.getStartMark(), "expected a single mapping item, but found "
- + mnode.getValue().size() + " items", mnode.getStartMark());
- }
- Node keyNode = mnode.getValue().get(0).getKeyNode();
- Node valueNode = mnode.getValue().get(0).getValueNode();
- Object key = constructObject(keyNode);
- Object value = constructObject(valueNode);
- omap.put(key, value);
- }
- return omap;
- }
+ public class ConstructYamlSet implements Construct {
+
+ @Override
+ public Object construct(Node node) {
+ if (node.isTwoStepsConstruction()) {
+ return (constructedObjects.containsKey(node) ? constructedObjects.get(node)
+ : createDefaultSet(((MappingNode) node).getValue().size()));
+ } else {
+ return constructSet((MappingNode) node);
+ }
}
- // Note: the same code as `construct_yaml_omap`.
- public class ConstructYamlPairs extends AbstractConstruct {
- public Object construct(Node node) {
- // Note: we do not check for duplicate keys, because it's too
- // CPU-expensive.
- if (!(node instanceof SequenceNode)) {
- throw new ConstructorException("while constructing pairs", node.getStartMark(),
- "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
- }
- SequenceNode snode = (SequenceNode) node;
- List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
- for (Node subnode : snode.getValue()) {
- if (!(subnode instanceof MappingNode)) {
- throw new ConstructorException("while constructingpairs", node.getStartMark(),
- "expected a mapping of length 1, but found " + subnode.getNodeId(),
- subnode.getStartMark());
- }
- MappingNode mnode = (MappingNode) subnode;
- if (mnode.getValue().size() != 1) {
- throw new ConstructorException("while constructing pairs", node.getStartMark(),
- "expected a single mapping item, but found " + mnode.getValue().size()
- + " items", mnode.getStartMark());
- }
- Node keyNode = mnode.getValue().get(0).getKeyNode();
- Node valueNode = mnode.getValue().get(0).getValueNode();
- Object key = constructObject(keyNode);
- Object value = constructObject(valueNode);
- pairs.add(new Object[] { key, value });
- }
- return pairs;
- }
+ @Override
+ @SuppressWarnings("unchecked")
+ public void construct2ndStep(Node node, Object object) {
+ if (node.isTwoStepsConstruction()) {
+ constructSet2ndStep((MappingNode) node, (Set<Object>) object);
+ } else {
+ throw new YAMLException("Unexpected recursive set structure. Node: " + node);
+ }
}
+ }
- public class ConstructYamlSet implements Construct {
- public Object construct(Node node) {
- if (node.isTwoStepsConstruction()) {
- return createDefaultSet();
- } else {
- return constructSet((MappingNode) node);
- }
- }
+ public class ConstructYamlStr extends AbstractConstruct {
- @SuppressWarnings("unchecked")
- public void construct2ndStep(Node node, Object object) {
- if (node.isTwoStepsConstruction()) {
- constructSet2ndStep((MappingNode) node, (Set<Object>) object);
- } else {
- throw new YAMLException("Unexpected recursive set structure. Node: " + node);
- }
- }
+ @Override
+ public Object construct(Node node) {
+ return constructScalar((ScalarNode) node);
}
+ }
- public class ConstructYamlStr extends AbstractConstruct {
- public Object construct(Node node) {
- return constructScalar((ScalarNode) node);
- }
- }
+ public class ConstructYamlSeq implements Construct {
- public class ConstructYamlSeq implements Construct {
- public Object construct(Node node) {
- SequenceNode seqNode = (SequenceNode) node;
- if (node.isTwoStepsConstruction()) {
- return createDefaultList(seqNode.getValue().size());
- } else {
- return constructSequence(seqNode);
- }
- }
+ @Override
+ public Object construct(Node node) {
+ SequenceNode seqNode = (SequenceNode) node;
+ if (node.isTwoStepsConstruction()) {
+ return newList(seqNode);
+ } else {
+ return constructSequence(seqNode);
+ }
+ }
- @SuppressWarnings("unchecked")
- public void construct2ndStep(Node node, Object data) {
- if (node.isTwoStepsConstruction()) {
- constructSequenceStep2((SequenceNode) node, (List<Object>) data);
- } else {
- throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
- }
- }
+ @Override
+ @SuppressWarnings("unchecked")
+ public void construct2ndStep(Node node, Object data) {
+ if (node.isTwoStepsConstruction()) {
+ constructSequenceStep2((SequenceNode) node, (List<Object>) data);
+ } else {
+ throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
+ }
}
+ }
- public class ConstructYamlMap implements Construct {
- public Object construct(Node node) {
- if (node.isTwoStepsConstruction()) {
- return createDefaultMap();
- } else {
- return constructMapping((MappingNode) node);
- }
- }
+ public class ConstructYamlMap implements Construct {
- @SuppressWarnings("unchecked")
- public void construct2ndStep(Node node, Object object) {
- if (node.isTwoStepsConstruction()) {
- constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
- } else {
- throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
- }
- }
+ @Override
+ public Object construct(Node node) {
+ MappingNode mnode = (MappingNode) node;
+ if (node.isTwoStepsConstruction()) {
+ return createDefaultMap(mnode.getValue().size());
+ } else {
+ return constructMapping(mnode);
+ }
}
- public static final class ConstructUndefined extends AbstractConstruct {
- public Object construct(Node node) {
- throw new ConstructorException(null, null,
- "could not determine a constructor for the tag " + node.getTag(),
- node.getStartMark());
- }
+ @Override
+ @SuppressWarnings("unchecked")
+ public void construct2ndStep(Node node, Object object) {
+ if (node.isTwoStepsConstruction()) {
+ constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
+ } else {
+ throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
+ }
+ }
+ }
+
+ public static final class ConstructUndefined extends AbstractConstruct {
+
+ @Override
+ public Object construct(Node node) {
+ throw new ConstructorException(null, null,
+ "could not determine a constructor for the tag " + node.getTag(), node.getStartMark());
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitable.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitable.java
index 87737280..dd72a702 100644
--- a/src/main/java/org/yaml/snakeyaml/emitter/Emitable.java
+++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitable.java
@@ -1,24 +1,22 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.emitter;
import java.io.IOException;
-
import org.yaml.snakeyaml.events.Event;
public interface Emitable {
- void emit(Event event) throws IOException;
+
+ void emit(Event event) throws IOException;
}
diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java
index 2136e21c..7ba6b4bb 100644
--- a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java
+++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java
@@ -1,41 +1,47 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.emitter;
import java.io.IOException;
import java.io.Writer;
+import java.util.ArrayDeque;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
-import java.util.concurrent.ArrayBlockingQueue;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.DumperOptions.ScalarStyle;
import org.yaml.snakeyaml.DumperOptions.Version;
+import org.yaml.snakeyaml.comments.CommentEventsCollector;
+import org.yaml.snakeyaml.comments.CommentLine;
+import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.CollectionEndEvent;
import org.yaml.snakeyaml.events.CollectionStartEvent;
+import org.yaml.snakeyaml.events.CommentEvent;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
+import org.yaml.snakeyaml.events.Event.ID;
import org.yaml.snakeyaml.events.MappingEndEvent;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.NodeEvent;
@@ -60,61 +66,141 @@ import org.yaml.snakeyaml.util.ArrayStack;
* </pre>
*/
public final class Emitter implements Emitable {
- private static final Map<Character, String> ESCAPE_REPLACEMENTS = new HashMap<Character, String>();
- public static final int MIN_INDENT = 1;
- public static final int MAX_INDENT = 10;
-
- private static final char[] SPACE = { ' ' };
-
- static {
- ESCAPE_REPLACEMENTS.put('\0', "0");
- ESCAPE_REPLACEMENTS.put('\u0007', "a");
- ESCAPE_REPLACEMENTS.put('\u0008', "b");
- ESCAPE_REPLACEMENTS.put('\u0009', "t");
- ESCAPE_REPLACEMENTS.put('\n', "n");
- ESCAPE_REPLACEMENTS.put('\u000B', "v");
- ESCAPE_REPLACEMENTS.put('\u000C', "f");
- ESCAPE_REPLACEMENTS.put('\r', "r");
- ESCAPE_REPLACEMENTS.put('\u001B', "e");
- ESCAPE_REPLACEMENTS.put('"', "\"");
- ESCAPE_REPLACEMENTS.put('\\', "\\");
- ESCAPE_REPLACEMENTS.put('\u0085', "N");
- ESCAPE_REPLACEMENTS.put('\u00A0', "_");
- ESCAPE_REPLACEMENTS.put('\u2028', "L");
- ESCAPE_REPLACEMENTS.put('\u2029', "P");
- }
-
- private final static Map<String, String> DEFAULT_TAG_PREFIXES = new LinkedHashMap<String, String>();
- static {
- DEFAULT_TAG_PREFIXES.put("!", "!");
- DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!");
- }
- // The stream should have the methods `write` and possibly `flush`.
- private final Writer stream;
-
- // Encoding is defined by Writer (cannot be overriden by STREAM-START.)
- // private Charset encoding;
+ public static final int MIN_INDENT = 1;
+ public static final int MAX_INDENT = 10;
+ private static final char[] SPACE = {' '};
+
+ private static final Pattern SPACES_PATTERN = Pattern.compile("\\s");
+ private static final Set<Character> INVALID_ANCHOR = new HashSet();
+
+ static {
+ INVALID_ANCHOR.add('[');
+ INVALID_ANCHOR.add(']');
+ INVALID_ANCHOR.add('{');
+ INVALID_ANCHOR.add('}');
+ INVALID_ANCHOR.add(',');
+ INVALID_ANCHOR.add('*');
+ INVALID_ANCHOR.add('&');
+ }
+
+ private static final Map<Character, String> ESCAPE_REPLACEMENTS =
+ new HashMap<Character, String>();
+
+ static {
+ ESCAPE_REPLACEMENTS.put('\0', "0");
+ ESCAPE_REPLACEMENTS.put('\u0007', "a");
+ ESCAPE_REPLACEMENTS.put('\u0008', "b");
+ ESCAPE_REPLACEMENTS.put('\u0009', "t");
+ ESCAPE_REPLACEMENTS.put('\n', "n");
+ ESCAPE_REPLACEMENTS.put('\u000B', "v");
+ ESCAPE_REPLACEMENTS.put('\u000C', "f");
+ ESCAPE_REPLACEMENTS.put('\r', "r");
+ ESCAPE_REPLACEMENTS.put('\u001B', "e");
+ ESCAPE_REPLACEMENTS.put('"', "\"");
+ ESCAPE_REPLACEMENTS.put('\\', "\\");
+ ESCAPE_REPLACEMENTS.put('\u0085', "N");
+ ESCAPE_REPLACEMENTS.put('\u00A0', "_");
+ ESCAPE_REPLACEMENTS.put('\u2028', "L");
+ ESCAPE_REPLACEMENTS.put('\u2029', "P");
+ }
+
+ private final static Map<String, String> DEFAULT_TAG_PREFIXES =
+ new LinkedHashMap<String, String>();
+
+ static {
+ DEFAULT_TAG_PREFIXES.put("!", "!");
+ DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!");
+ }
+
+ // The stream should have the methods `write` and possibly `flush`.
+ private final Writer stream;
+
+ // Encoding is defined by Writer (cannot be overridden by STREAM-START.)
+ // private Charset encoding;
+
+ // Emitter is a state machine with a stack of states to handle nested
+ // structures.
+ private final ArrayStack<EmitterState> states;
+ private EmitterState state;
+
+ // Current event and the event queue.
+ private final Queue<Event> events;
+ private Event event;
+
+ // The current indentation level and the stack of previous indents.
+ private final ArrayStack<Integer> indents;
+ private Integer indent;
+
+ // Flow level.
+ private int flowLevel;
+
+ // Contexts.
+ private boolean rootContext;
+ private boolean mappingContext;
+ private boolean simpleKeyContext;
+
+ //
+ // Characteristics of the last emitted character:
+ // - current position.
+ // - is it a whitespace?
+ // - is it an indention character
+ // (indentation space, '-', '?', or ':')?
+ // private int line; this variable is not used
+ private int column;
+ private boolean whitespace;
+ private boolean indention;
+ private boolean openEnded;
+
+ // Formatting details.
+ private final Boolean canonical;
+ // pretty print flow by adding extra line breaks
+ private final Boolean prettyFlow;
+
+ private final boolean allowUnicode;
+ private int bestIndent;
+ private final int indicatorIndent;
+ private final boolean indentWithIndicator;
+ private int bestWidth;
+ private final char[] bestLineBreak;
+ private final boolean splitLines;
+ private final int maxSimpleKeyLength;
+ private final boolean emitComments;
+
+ // Tag prefixes.
+ private Map<String, String> tagPrefixes;
+
+ // Prepared anchor and tag.
+ private String preparedAnchor;
+ private String preparedTag;
+
+ // Scalar analysis and style.
+ private ScalarAnalysis analysis;
+ private DumperOptions.ScalarStyle style;
+
+ // Comment processing
+ private final CommentEventsCollector blockCommentsCollector;
+ private final CommentEventsCollector inlineCommentsCollector;
+
+
+ public Emitter(Writer stream, DumperOptions opts) {
+ // The stream should have the methods `write` and possibly `flush`.
+ this.stream = stream;
// Emitter is a state machine with a stack of states to handle nested
// structures.
- private final ArrayStack<EmitterState> states;
- private EmitterState state;
-
+ this.states = new ArrayStack<EmitterState>(100);
+ this.state = new ExpectStreamStart();
// Current event and the event queue.
- private final Queue<Event> events;
- private Event event;
-
+ this.events = new ArrayDeque<>(100);
+ this.event = null;
// The current indentation level and the stack of previous indents.
- private final ArrayStack<Integer> indents;
- private Integer indent;
-
+ this.indents = new ArrayStack<Integer>(10);
+ this.indent = null;
// Flow level.
- private int flowLevel;
-
+ this.flowLevel = 0;
// Contexts.
- private boolean rootContext;
- private boolean mappingContext;
- private boolean simpleKeyContext;
+ mappingContext = false;
+ simpleKeyContext = false;
//
// Characteristics of the last emitted character:
@@ -122,1358 +208,1510 @@ public final class Emitter implements Emitable {
// - is it a whitespace?
// - is it an indention character
// (indentation space, '-', '?', or ':')?
- // private int line; this variable is not used
- private int column;
- private boolean whitespace;
- private boolean indention;
- private boolean openEnded;
+ column = 0;
+ whitespace = true;
+ indention = true;
- // Formatting details.
- private Boolean canonical;
- // pretty print flow by adding extra line breaks
- private Boolean prettyFlow;
+ // Whether the document requires an explicit document indicator
+ openEnded = false;
- private boolean allowUnicode;
- private int bestIndent;
- private int indicatorIndent;
- private int bestWidth;
- private char[] bestLineBreak;
- private boolean splitLines;
+ // Formatting details.
+ this.canonical = opts.isCanonical();
+ this.prettyFlow = opts.isPrettyFlow();
+ this.allowUnicode = opts.isAllowUnicode();
+ this.bestIndent = 2;
+ if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) {
+ this.bestIndent = opts.getIndent();
+ }
+ this.indicatorIndent = opts.getIndicatorIndent();
+ this.indentWithIndicator = opts.getIndentWithIndicator();
+ this.bestWidth = 80;
+ if (opts.getWidth() > this.bestIndent * 2) {
+ this.bestWidth = opts.getWidth();
+ }
+ this.bestLineBreak = opts.getLineBreak().getString().toCharArray();
+ this.splitLines = opts.getSplitLines();
+ this.maxSimpleKeyLength = opts.getMaxSimpleKeyLength();
+ this.emitComments = opts.isProcessComments();
// Tag prefixes.
- private Map<String, String> tagPrefixes;
+ this.tagPrefixes = new LinkedHashMap<String, String>();
// Prepared anchor and tag.
- private String preparedAnchor;
- private String preparedTag;
+ this.preparedAnchor = null;
+ this.preparedTag = null;
// Scalar analysis and style.
- private ScalarAnalysis analysis;
- private Character style;
-
- public Emitter(Writer stream, DumperOptions opts) {
- // The stream should have the methods `write` and possibly `flush`.
- this.stream = stream;
- // Emitter is a state machine with a stack of states to handle nested
- // structures.
- this.states = new ArrayStack<EmitterState>(100);
- this.state = new ExpectStreamStart();
- // Current event and the event queue.
- this.events = new ArrayBlockingQueue<Event>(100);
- this.event = null;
- // The current indentation level and the stack of previous indents.
- this.indents = new ArrayStack<Integer>(10);
- this.indent = null;
- // Flow level.
- this.flowLevel = 0;
- // Contexts.
- mappingContext = false;
- simpleKeyContext = false;
-
- //
- // Characteristics of the last emitted character:
- // - current position.
- // - is it a whitespace?
- // - is it an indention character
- // (indentation space, '-', '?', or ':')?
- column = 0;
- whitespace = true;
- indention = true;
-
- // Whether the document requires an explicit document indicator
- openEnded = false;
-
- // Formatting details.
- this.canonical = opts.isCanonical();
- this.prettyFlow = opts.isPrettyFlow();
- this.allowUnicode = opts.isAllowUnicode();
- this.bestIndent = 2;
- if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) {
- this.bestIndent = opts.getIndent();
- }
- this.indicatorIndent = opts.getIndicatorIndent();
- this.bestWidth = 80;
- if (opts.getWidth() > this.bestIndent * 2) {
- this.bestWidth = opts.getWidth();
- }
- this.bestLineBreak = opts.getLineBreak().getString().toCharArray();
- this.splitLines = opts.getSplitLines();
+ this.analysis = null;
+ this.style = null;
+
+ // Comment processing
+ this.blockCommentsCollector =
+ new CommentEventsCollector(events, CommentType.BLANK_LINE, CommentType.BLOCK);
+ this.inlineCommentsCollector = new CommentEventsCollector(events, CommentType.IN_LINE);
+ }
+
+ public void emit(Event event) throws IOException {
+ this.events.add(event);
+ while (!needMoreEvents()) {
+ this.event = this.events.poll();
+ this.state.expect();
+ this.event = null;
+ }
+ }
- // Tag prefixes.
- this.tagPrefixes = new LinkedHashMap<String, String>();
+ // In some cases, we wait for a few next events before emitting.
- // Prepared anchor and tag.
- this.preparedAnchor = null;
- this.preparedTag = null;
+ private boolean needMoreEvents() {
+ if (events.isEmpty()) {
+ return true;
+ }
- // Scalar analysis and style.
- this.analysis = null;
- this.style = null;
+ Iterator<Event> iter = events.iterator();
+ Event event = iter.next(); // FIXME why without check ???
+ while (event instanceof CommentEvent) {
+ if (!iter.hasNext()) {
+ return true;
+ }
+ event = iter.next();
}
- public void emit(Event event) throws IOException {
- this.events.add(event);
- while (!needMoreEvents()) {
- this.event = this.events.poll();
- this.state.expect();
- this.event = null;
- }
+ if (event instanceof DocumentStartEvent) {
+ return needEvents(iter, 1);
+ } else if (event instanceof SequenceStartEvent) {
+ return needEvents(iter, 2);
+ } else if (event instanceof MappingStartEvent) {
+ return needEvents(iter, 3);
+ } else if (event instanceof StreamStartEvent) {
+ return needEvents(iter, 2);
+ } else if (event instanceof StreamEndEvent) {
+ return false;
+ } else if (emitComments) {
+ return needEvents(iter, 1);
+ }
+ return false;
+ }
+
+ private boolean needEvents(Iterator<Event> iter, int count) {
+ int level = 0;
+ int actualCount = 0;
+ while (iter.hasNext()) {
+ Event event = iter.next();
+ if (event instanceof CommentEvent) {
+ continue;
+ }
+ actualCount++;
+ if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) {
+ level++;
+ } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) {
+ level--;
+ } else if (event instanceof StreamEndEvent) {
+ level = -1;
+ }
+ if (level < 0) {
+ return false;
+ }
+ }
+ return actualCount < count;
+ }
+
+ private void increaseIndent(boolean flow, boolean indentless) {
+ indents.push(indent);
+ if (indent == null) {
+ if (flow) {
+ indent = bestIndent;
+ } else {
+ indent = 0;
+ }
+ } else if (!indentless) {
+ this.indent += bestIndent;
}
+ }
- // In some cases, we wait for a few next events before emitting.
+ // States
- private boolean needMoreEvents() {
- if (events.isEmpty()) {
- return true;
- }
- Event event = events.peek();
- if (event instanceof DocumentStartEvent) {
- return needEvents(1);
- } else if (event instanceof SequenceStartEvent) {
- return needEvents(2);
- } else if (event instanceof MappingStartEvent) {
- return needEvents(3);
- } else {
- return false;
- }
- }
+ // Stream handlers.
- private boolean needEvents(int count) {
- int level = 0;
- Iterator<Event> iter = events.iterator();
- iter.next();
- while (iter.hasNext()) {
- Event event = iter.next();
- if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) {
- level++;
- } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) {
- level--;
- } else if (event instanceof StreamEndEvent) {
- level = -1;
- }
- if (level < 0) {
- return false;
- }
- }
- return events.size() < count + 1;
+ private class ExpectStreamStart implements EmitterState {
+
+ public void expect() throws IOException {
+ if (event instanceof StreamStartEvent) {
+ writeStreamStart();
+ state = new ExpectFirstDocumentStart();
+ } else {
+ throw new EmitterException("expected StreamStartEvent, but got " + event);
+ }
}
+ }
- private void increaseIndent(boolean flow, boolean indentless) {
- indents.push(indent);
- if (indent == null) {
- if (flow) {
- indent = bestIndent;
- } else {
- indent = 0;
- }
- } else if (!indentless) {
- this.indent += bestIndent;
- }
+ private class ExpectNothing implements EmitterState {
+
+ public void expect() throws IOException {
+ throw new EmitterException("expecting nothing, but got " + event);
}
+ }
- // States
+ // Document handlers.
- // Stream handlers.
+ private class ExpectFirstDocumentStart implements EmitterState {
- private class ExpectStreamStart implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof StreamStartEvent) {
- writeStreamStart();
- state = new ExpectFirstDocumentStart();
- } else {
- throw new EmitterException("expected StreamStartEvent, but got " + event);
- }
- }
+ public void expect() throws IOException {
+ new ExpectDocumentStart(true).expect();
}
+ }
- private class ExpectNothing implements EmitterState {
- public void expect() throws IOException {
- throw new EmitterException("expecting nothing, but got " + event);
- }
- }
+ private class ExpectDocumentStart implements EmitterState {
- // Document handlers.
+ private final boolean first;
- private class ExpectFirstDocumentStart implements EmitterState {
- public void expect() throws IOException {
- new ExpectDocumentStart(true).expect();
- }
+ public ExpectDocumentStart(boolean first) {
+ this.first = first;
}
- private class ExpectDocumentStart implements EmitterState {
- private boolean first;
+ public void expect() throws IOException {
+ if (event instanceof DocumentStartEvent) {
+ DocumentStartEvent ev = (DocumentStartEvent) event;
+ if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) {
+ writeIndicator("...", true, false, false);
+ writeIndent();
+ }
+ if (ev.getVersion() != null) {
+ String versionText = prepareVersion(ev.getVersion());
+ writeVersionDirective(versionText);
+ }
+ tagPrefixes = new LinkedHashMap<String, String>(DEFAULT_TAG_PREFIXES);
+ if (ev.getTags() != null) {
+ Set<String> handles = new TreeSet<String>(ev.getTags().keySet());
+ for (String handle : handles) {
+ String prefix = ev.getTags().get(handle);
+ tagPrefixes.put(prefix, handle);
+ String handleText = prepareTagHandle(handle);
+ String prefixText = prepareTagPrefix(prefix);
+ writeTagDirective(handleText, prefixText);
+ }
+ }
+ boolean implicit = first && !ev.getExplicit() && !canonical && ev.getVersion() == null
+ && (ev.getTags() == null || ev.getTags().isEmpty()) && !checkEmptyDocument();
+ if (!implicit) {
+ writeIndent();
+ writeIndicator("---", true, false, false);
+ if (canonical) {
+ writeIndent();
+ }
+ }
+ state = new ExpectDocumentRoot();
+ } else if (event instanceof StreamEndEvent) {
+ writeStreamEnd();
+ state = new ExpectNothing();
+ } else if (event instanceof CommentEvent) {
+ blockCommentsCollector.collectEvents(event);
+ writeBlockComment();
+ // state = state; remains unchanged
+ } else {
+ throw new EmitterException("expected DocumentStartEvent, but got " + event);
+ }
+ }
+ }
- public ExpectDocumentStart(boolean first) {
- this.first = first;
- }
+ private class ExpectDocumentEnd implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof DocumentStartEvent) {
- DocumentStartEvent ev = (DocumentStartEvent) event;
- if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) {
- writeIndicator("...", true, false, false);
- writeIndent();
- }
- if (ev.getVersion() != null) {
- String versionText = prepareVersion(ev.getVersion());
- writeVersionDirective(versionText);
- }
- tagPrefixes = new LinkedHashMap<String, String>(DEFAULT_TAG_PREFIXES);
- if (ev.getTags() != null) {
- Set<String> handles = new TreeSet<String>(ev.getTags().keySet());
- for (String handle : handles) {
- String prefix = ev.getTags().get(handle);
- tagPrefixes.put(prefix, handle);
- String handleText = prepareTagHandle(handle);
- String prefixText = prepareTagPrefix(prefix);
- writeTagDirective(handleText, prefixText);
- }
- }
- boolean implicit = first && !ev.getExplicit() && !canonical
- && ev.getVersion() == null
- && (ev.getTags() == null || ev.getTags().isEmpty())
- && !checkEmptyDocument();
- if (!implicit) {
- writeIndent();
- writeIndicator("---", true, false, false);
- if (canonical) {
- writeIndent();
- }
- }
- state = new ExpectDocumentRoot();
- } else if (event instanceof StreamEndEvent) {
- // TODO fix 313 PyYAML changeset
- // if (openEnded) {
- // writeIndicator("...", true, false, false);
- // writeIndent();
- // }
- writeStreamEnd();
- state = new ExpectNothing();
- } else {
- throw new EmitterException("expected DocumentStartEvent, but got " + event);
- }
+ public void expect() throws IOException {
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ writeBlockComment();
+ if (event instanceof DocumentEndEvent) {
+ writeIndent();
+ if (((DocumentEndEvent) event).getExplicit()) {
+ writeIndicator("...", true, false, false);
+ writeIndent();
}
+ flushStream();
+ state = new ExpectDocumentStart(false);
+ } else {
+ throw new EmitterException("expected DocumentEndEvent, but got " + event);
+ }
}
+ }
- private class ExpectDocumentEnd implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof DocumentEndEvent) {
- writeIndent();
- if (((DocumentEndEvent) event).getExplicit()) {
- writeIndicator("...", true, false, false);
- writeIndent();
- }
- flushStream();
- state = new ExpectDocumentStart(false);
- } else {
- throw new EmitterException("expected DocumentEndEvent, but got " + event);
- }
- }
- }
+ private class ExpectDocumentRoot implements EmitterState {
- private class ExpectDocumentRoot implements EmitterState {
- public void expect() throws IOException {
- states.push(new ExpectDocumentEnd());
- expectNode(true, false, false);
+ public void expect() throws IOException {
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ if (!blockCommentsCollector.isEmpty()) {
+ writeBlockComment();
+ if (event instanceof DocumentEndEvent) {
+ new ExpectDocumentEnd().expect();
+ return;
}
+ }
+ states.push(new ExpectDocumentEnd());
+ expectNode(true, false, false);
}
-
- // Node handlers.
-
- private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException {
- rootContext = root;
- mappingContext = mapping;
- simpleKeyContext = simpleKey;
- if (event instanceof AliasEvent) {
- expectAlias();
- } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) {
- processAnchor("&");
- processTag();
- if (event instanceof ScalarEvent) {
- expectScalar();
- } else if (event instanceof SequenceStartEvent) {
- if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).getFlowStyle()
- || checkEmptySequence()) {
- expectFlowSequence();
- } else {
- expectBlockSequence();
- }
- } else {// MappingStartEvent
- if (flowLevel != 0 || canonical || ((MappingStartEvent) event).getFlowStyle()
- || checkEmptyMapping()) {
- expectFlowMapping();
- } else {
- expectBlockMapping();
- }
- }
+ }
+
+ // Node handlers.
+
+ private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException {
+ rootContext = root;
+ mappingContext = mapping;
+ simpleKeyContext = simpleKey;
+ if (event instanceof AliasEvent) {
+ expectAlias();
+ } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) {
+ processAnchor("&");
+ processTag();
+ if (event instanceof ScalarEvent) {
+ expectScalar();
+ } else if (event instanceof SequenceStartEvent) {
+ if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow()
+ || checkEmptySequence()) {
+ expectFlowSequence();
+ } else {
+ expectBlockSequence();
+ }
+ } else {// MappingStartEvent
+ if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow()
+ || checkEmptyMapping()) {
+ expectFlowMapping();
} else {
- throw new EmitterException("expected NodeEvent, but got " + event);
+ expectBlockMapping();
}
+ }
+ } else {
+ throw new EmitterException("expected NodeEvent, but got " + event);
}
+ }
- private void expectAlias() throws IOException {
- if (((NodeEvent) event).getAnchor() == null) {
- throw new EmitterException("anchor is not specified for alias");
- }
- processAnchor("*");
- state = states.pop();
+ private void expectAlias() throws IOException {
+ if (!(event instanceof AliasEvent)) {
+ throw new EmitterException("Alias must be provided");
}
+ processAnchor("*");
+ state = states.pop();
+ }
+
+ private void expectScalar() throws IOException {
+ increaseIndent(true, false);
+ processScalar();
+ indent = indents.pop();
+ state = states.pop();
+ }
+
+ // Flow sequence handlers.
+
+ private void expectFlowSequence() throws IOException {
+ writeIndicator("[", true, true, false);
+ flowLevel++;
+ increaseIndent(true, false);
+ if (prettyFlow) {
+ writeIndent();
+ }
+ state = new ExpectFirstFlowSequenceItem();
+ }
- private void expectScalar() throws IOException {
- increaseIndent(true, false);
- processScalar();
+ private class ExpectFirstFlowSequenceItem implements EmitterState {
+
+ public void expect() throws IOException {
+ if (event instanceof SequenceEndEvent) {
indent = indents.pop();
+ flowLevel--;
+ writeIndicator("]", false, false, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
state = states.pop();
+ } else if (event instanceof CommentEvent) {
+ blockCommentsCollector.collectEvents(event);
+ writeBlockComment();
+ } else {
+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
+ writeIndent();
+ }
+ states.push(new ExpectFlowSequenceItem());
+ expectNode(false, false, false);
+ event = inlineCommentsCollector.collectEvents(event);
+ writeInlineComments();
+ }
}
+ }
- // Flow sequence handlers.
+ private class ExpectFlowSequenceItem implements EmitterState {
- private void expectFlowSequence() throws IOException {
- writeIndicator("[", true, true, false);
- flowLevel++;
- increaseIndent(true, false);
+ public void expect() throws IOException {
+ if (event instanceof SequenceEndEvent) {
+ indent = indents.pop();
+ flowLevel--;
+ if (canonical) {
+ writeIndicator(",", false, false, false);
+ writeIndent();
+ } else if (prettyFlow) {
+ writeIndent();
+ }
+ writeIndicator("]", false, false, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
if (prettyFlow) {
- writeIndent();
+ writeIndent();
}
- state = new ExpectFirstFlowSequenceItem();
+ state = states.pop();
+ } else if (event instanceof CommentEvent) {
+ event = blockCommentsCollector.collectEvents(event);
+ } else {
+ writeIndicator(",", false, false, false);
+ writeBlockComment();
+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
+ writeIndent();
+ }
+ states.push(new ExpectFlowSequenceItem());
+ expectNode(false, false, false);
+ event = inlineCommentsCollector.collectEvents(event);
+ writeInlineComments();
+ }
}
+ }
- private class ExpectFirstFlowSequenceItem implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof SequenceEndEvent) {
- indent = indents.pop();
- flowLevel--;
- writeIndicator("]", false, false, false);
- state = states.pop();
- } else {
- if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
- writeIndent();
- }
- states.push(new ExpectFlowSequenceItem());
- expectNode(false, false, false);
- }
- }
+ // Flow mapping handlers.
+
+ private void expectFlowMapping() throws IOException {
+ writeIndicator("{", true, true, false);
+ flowLevel++;
+ increaseIndent(true, false);
+ if (prettyFlow) {
+ writeIndent();
}
+ state = new ExpectFirstFlowMappingKey();
+ }
- private class ExpectFlowSequenceItem implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof SequenceEndEvent) {
- indent = indents.pop();
- flowLevel--;
- if (canonical) {
- writeIndicator(",", false, false, false);
- writeIndent();
- }
- writeIndicator("]", false, false, false);
- if (prettyFlow) {
- writeIndent();
- }
- state = states.pop();
- } else {
- writeIndicator(",", false, false, false);
- if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
- writeIndent();
- }
- states.push(new ExpectFlowSequenceItem());
- expectNode(false, false, false);
- }
+ private class ExpectFirstFlowMappingKey implements EmitterState {
+
+ public void expect() throws IOException {
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ writeBlockComment();
+ if (event instanceof MappingEndEvent) {
+ indent = indents.pop();
+ flowLevel--;
+ writeIndicator("}", false, false, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
+ state = states.pop();
+ } else {
+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
+ writeIndent();
}
+ if (!canonical && checkSimpleKey()) {
+ states.push(new ExpectFlowMappingSimpleValue());
+ expectNode(false, true, true);
+ } else {
+ writeIndicator("?", true, false, false);
+ states.push(new ExpectFlowMappingValue());
+ expectNode(false, true, false);
+ }
+ }
}
+ }
- // Flow mapping handlers.
+ private class ExpectFlowMappingKey implements EmitterState {
- private void expectFlowMapping() throws IOException {
- writeIndicator("{", true, true, false);
- flowLevel++;
- increaseIndent(true, false);
+ public void expect() throws IOException {
+ if (event instanceof MappingEndEvent) {
+ indent = indents.pop();
+ flowLevel--;
+ if (canonical) {
+ writeIndicator(",", false, false, false);
+ writeIndent();
+ }
if (prettyFlow) {
- writeIndent();
+ writeIndent();
}
- state = new ExpectFirstFlowMappingKey();
- }
-
- private class ExpectFirstFlowMappingKey implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof MappingEndEvent) {
- indent = indents.pop();
- flowLevel--;
- writeIndicator("}", false, false, false);
- state = states.pop();
- } else {
- if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
- writeIndent();
- }
- if (!canonical && checkSimpleKey()) {
- states.push(new ExpectFlowMappingSimpleValue());
- expectNode(false, true, true);
- } else {
- writeIndicator("?", true, false, false);
- states.push(new ExpectFlowMappingValue());
- expectNode(false, true, false);
- }
- }
+ writeIndicator("}", false, false, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
+ state = states.pop();
+ } else {
+ writeIndicator(",", false, false, false);
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ writeBlockComment();
+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
+ writeIndent();
+ }
+ if (!canonical && checkSimpleKey()) {
+ states.push(new ExpectFlowMappingSimpleValue());
+ expectNode(false, true, true);
+ } else {
+ writeIndicator("?", true, false, false);
+ states.push(new ExpectFlowMappingValue());
+ expectNode(false, true, false);
}
+ }
}
-
- private class ExpectFlowMappingKey implements EmitterState {
- public void expect() throws IOException {
- if (event instanceof MappingEndEvent) {
- indent = indents.pop();
- flowLevel--;
- if (canonical) {
- writeIndicator(",", false, false, false);
- writeIndent();
- }
- if (prettyFlow) {
- writeIndent();
- }
- writeIndicator("}", false, false, false);
- state = states.pop();
- } else {
- writeIndicator(",", false, false, false);
- if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
- writeIndent();
- }
- if (!canonical && checkSimpleKey()) {
- states.push(new ExpectFlowMappingSimpleValue());
- expectNode(false, true, true);
- } else {
- writeIndicator("?", true, false, false);
- states.push(new ExpectFlowMappingValue());
- expectNode(false, true, false);
- }
- }
- }
+ }
+
+ private class ExpectFlowMappingSimpleValue implements EmitterState {
+
+ public void expect() throws IOException {
+ writeIndicator(":", false, false, false);
+ event = inlineCommentsCollector.collectEventsAndPoll(event);
+ writeInlineComments();
+ states.push(new ExpectFlowMappingKey());
+ expectNode(false, true, false);
+ inlineCommentsCollector.collectEvents(event);
+ writeInlineComments();
}
-
- private class ExpectFlowMappingSimpleValue implements EmitterState {
- public void expect() throws IOException {
- writeIndicator(":", false, false, false);
- states.push(new ExpectFlowMappingKey());
- expectNode(false, true, false);
- }
+ }
+
+ private class ExpectFlowMappingValue implements EmitterState {
+
+ public void expect() throws IOException {
+ if (canonical || (column > bestWidth) || prettyFlow) {
+ writeIndent();
+ }
+ writeIndicator(":", true, false, false);
+ event = inlineCommentsCollector.collectEventsAndPoll(event);
+ writeInlineComments();
+ states.push(new ExpectFlowMappingKey());
+ expectNode(false, true, false);
+ inlineCommentsCollector.collectEvents(event);
+ writeInlineComments();
}
+ }
- private class ExpectFlowMappingValue implements EmitterState {
- public void expect() throws IOException {
- if (canonical || (column > bestWidth) || prettyFlow) {
- writeIndent();
- }
- writeIndicator(":", true, false, false);
- states.push(new ExpectFlowMappingKey());
- expectNode(false, true, false);
- }
- }
+ // Block sequence handlers.
- // Block sequence handlers.
+ private void expectBlockSequence() throws IOException {
+ boolean indentless = mappingContext && !indention;
+ increaseIndent(false, indentless);
+ state = new ExpectFirstBlockSequenceItem();
+ }
- private void expectBlockSequence() throws IOException {
- boolean indentless = mappingContext && !indention;
- increaseIndent(false, indentless);
- state = new ExpectFirstBlockSequenceItem();
- }
+ private class ExpectFirstBlockSequenceItem implements EmitterState {
- private class ExpectFirstBlockSequenceItem implements EmitterState {
- public void expect() throws IOException {
- new ExpectBlockSequenceItem(true).expect();
- }
+ public void expect() throws IOException {
+ new ExpectBlockSequenceItem(true).expect();
}
+ }
- private class ExpectBlockSequenceItem implements EmitterState {
- private boolean first;
+ private class ExpectBlockSequenceItem implements EmitterState {
- public ExpectBlockSequenceItem(boolean first) {
- this.first = first;
- }
+ private final boolean first;
- public void expect() throws IOException {
- if (!this.first && event instanceof SequenceEndEvent) {
- indent = indents.pop();
- state = states.pop();
- } else {
- writeIndent();
- writeWhitespace(indicatorIndent);
- writeIndicator("-", true, false, true);
- states.push(new ExpectBlockSequenceItem(false));
- expectNode(false, false, false);
- }
- }
+ public ExpectBlockSequenceItem(boolean first) {
+ this.first = first;
}
- // Block mapping handlers.
- private void expectBlockMapping() throws IOException {
- increaseIndent(false, false);
- state = new ExpectFirstBlockMappingKey();
+ public void expect() throws IOException {
+ if (!this.first && event instanceof SequenceEndEvent) {
+ indent = indents.pop();
+ state = states.pop();
+ } else if (event instanceof CommentEvent) {
+ blockCommentsCollector.collectEvents(event);
+ } else {
+ writeIndent();
+ if (!indentWithIndicator || this.first) {
+ writeWhitespace(indicatorIndent);
+ }
+ writeIndicator("-", true, false, true);
+ if (indentWithIndicator && this.first) {
+ indent += indicatorIndent;
+ }
+ if (!blockCommentsCollector.isEmpty()) {
+ increaseIndent(false, false);
+ writeBlockComment();
+ if (event instanceof ScalarEvent) {
+ analysis = analyzeScalar(((ScalarEvent) event).getValue());
+ if (!analysis.isEmpty()) {
+ writeIndent();
+ }
+ }
+ indent = indents.pop();
+ }
+ states.push(new ExpectBlockSequenceItem(false));
+ expectNode(false, false, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
+ }
}
+ }
- private class ExpectFirstBlockMappingKey implements EmitterState {
- public void expect() throws IOException {
- new ExpectBlockMappingKey(true).expect();
- }
+ // Block mapping handlers.
+ private void expectBlockMapping() throws IOException {
+ increaseIndent(false, false);
+ state = new ExpectFirstBlockMappingKey();
+ }
+
+ private class ExpectFirstBlockMappingKey implements EmitterState {
+
+ public void expect() throws IOException {
+ new ExpectBlockMappingKey(true).expect();
}
+ }
- private class ExpectBlockMappingKey implements EmitterState {
- private boolean first;
+ private class ExpectBlockMappingKey implements EmitterState {
- public ExpectBlockMappingKey(boolean first) {
- this.first = first;
- }
+ private final boolean first;
- public void expect() throws IOException {
- if (!this.first && event instanceof MappingEndEvent) {
- indent = indents.pop();
- state = states.pop();
- } else {
- writeIndent();
- if (checkSimpleKey()) {
- states.push(new ExpectBlockMappingSimpleValue());
- expectNode(false, true, true);
- } else {
- writeIndicator("?", true, false, true);
- states.push(new ExpectBlockMappingValue());
- expectNode(false, true, false);
- }
- }
- }
+ public ExpectBlockMappingKey(boolean first) {
+ this.first = first;
}
- private class ExpectBlockMappingSimpleValue implements EmitterState {
- public void expect() throws IOException {
- writeIndicator(":", false, false, false);
- states.push(new ExpectBlockMappingKey(false));
- expectNode(false, true, false);
+ public void expect() throws IOException {
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ writeBlockComment();
+ if (!this.first && event instanceof MappingEndEvent) {
+ indent = indents.pop();
+ state = states.pop();
+ } else {
+ writeIndent();
+ if (checkSimpleKey()) {
+ states.push(new ExpectBlockMappingSimpleValue());
+ expectNode(false, true, true);
+ } else {
+ writeIndicator("?", true, false, true);
+ states.push(new ExpectBlockMappingValue());
+ expectNode(false, true, false);
}
+ }
}
+ }
- private class ExpectBlockMappingValue implements EmitterState {
- public void expect() throws IOException {
- writeIndent();
- writeIndicator(":", true, false, true);
- states.push(new ExpectBlockMappingKey(false));
- expectNode(false, true, false);
- }
+ private boolean isFoldedOrLiteral(Event event) {
+ if (!event.is(ID.Scalar)) {
+ return false;
}
-
- // Checkers.
-
- private boolean checkEmptySequence() {
- return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent;
+ ScalarEvent scalarEvent = (ScalarEvent) event;
+ ScalarStyle style = scalarEvent.getScalarStyle();
+ return style == ScalarStyle.FOLDED || style == ScalarStyle.LITERAL;
+ }
+
+ private class ExpectBlockMappingSimpleValue implements EmitterState {
+
+ public void expect() throws IOException {
+ writeIndicator(":", false, false, false);
+ event = inlineCommentsCollector.collectEventsAndPoll(event);
+ if (!isFoldedOrLiteral(event)) {
+ if (writeInlineComments()) {
+ increaseIndent(true, false);
+ writeIndent();
+ indent = indents.pop();
+ }
+ }
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ if (!blockCommentsCollector.isEmpty()) {
+ increaseIndent(true, false);
+ writeBlockComment();
+ writeIndent();
+ indent = indents.pop();
+ }
+ states.push(new ExpectBlockMappingKey(false));
+ expectNode(false, true, false);
+ inlineCommentsCollector.collectEvents();
+ writeInlineComments();
}
-
- private boolean checkEmptyMapping() {
- return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent;
+ }
+
+ private class ExpectBlockMappingValue implements EmitterState {
+
+ public void expect() throws IOException {
+ writeIndent();
+ writeIndicator(":", true, false, true);
+ event = inlineCommentsCollector.collectEventsAndPoll(event);
+ writeInlineComments();
+ event = blockCommentsCollector.collectEventsAndPoll(event);
+ writeBlockComment();
+ states.push(new ExpectBlockMappingKey(false));
+ expectNode(false, true, false);
+ inlineCommentsCollector.collectEvents(event);
+ writeInlineComments();
}
+ }
- private boolean checkEmptyDocument() {
- if (!(event instanceof DocumentStartEvent) || events.isEmpty()) {
- return false;
- }
- Event event = events.peek();
- if (event instanceof ScalarEvent) {
- ScalarEvent e = (ScalarEvent) event;
- return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e
- .getValue().length() == 0;
- }
- return false;
- }
+ // Checkers.
- private boolean checkSimpleKey() {
- int length = 0;
- if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) {
- if (preparedAnchor == null) {
- preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor());
- }
- length += preparedAnchor.length();
- }
- String tag = null;
- if (event instanceof ScalarEvent) {
- tag = ((ScalarEvent) event).getTag();
- } else if (event instanceof CollectionStartEvent) {
- tag = ((CollectionStartEvent) event).getTag();
- }
- if (tag != null) {
- if (preparedTag == null) {
- preparedTag = prepareTag(tag);
- }
- length += preparedTag.length();
- }
- if (event instanceof ScalarEvent) {
- if (analysis == null) {
- analysis = analyzeScalar(((ScalarEvent) event).getValue());
- }
- length += analysis.scalar.length();
- }
- return length < 128 && (event instanceof AliasEvent
- || (event instanceof ScalarEvent && !analysis.empty && !analysis.multiline)
- || checkEmptySequence() || checkEmptyMapping());
- }
+ private boolean checkEmptySequence() {
+ return event instanceof SequenceStartEvent && !events.isEmpty()
+ && events.peek() instanceof SequenceEndEvent;
+ }
- // Anchor, Tag, and Scalar processors.
+ private boolean checkEmptyMapping() {
+ return event instanceof MappingStartEvent && !events.isEmpty()
+ && events.peek() instanceof MappingEndEvent;
+ }
- private void processAnchor(String indicator) throws IOException {
- NodeEvent ev = (NodeEvent) event;
- if (ev.getAnchor() == null) {
- preparedAnchor = null;
- return;
- }
- if (preparedAnchor == null) {
- preparedAnchor = prepareAnchor(ev.getAnchor());
- }
- writeIndicator(indicator + preparedAnchor, true, false, false);
- preparedAnchor = null;
+ private boolean checkEmptyDocument() {
+ if (!(event instanceof DocumentStartEvent) || events.isEmpty()) {
+ return false;
}
-
- private void processTag() throws IOException {
- String tag = null;
- if (event instanceof ScalarEvent) {
- ScalarEvent ev = (ScalarEvent) event;
- tag = ev.getTag();
- if (style == null) {
- style = chooseScalarStyle();
- }
- if ((!canonical || tag == null) && ((style == null && ev.getImplicit()
- .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit()
- .canOmitTagInNonPlainScalar()))) {
- preparedTag = null;
- return;
- }
- if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) {
- tag = "!";
- preparedTag = null;
- }
- } else {
- CollectionStartEvent ev = (CollectionStartEvent) event;
- tag = ev.getTag();
- if ((!canonical || tag == null) && ev.getImplicit()) {
- preparedTag = null;
- return;
- }
- }
- if (tag == null) {
- throw new EmitterException("tag is not specified");
- }
- if (preparedTag == null) {
- preparedTag = prepareTag(tag);
- }
- writeIndicator(preparedTag, true, false, false);
+ Event event = events.peek();
+ if (event instanceof ScalarEvent) {
+ ScalarEvent e = (ScalarEvent) event;
+ return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null
+ && e.getValue().length() == 0;
+ }
+ return false;
+ }
+
+ private boolean checkSimpleKey() {
+ int length = 0;
+ if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) {
+ if (preparedAnchor == null) {
+ preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor());
+ }
+ length += preparedAnchor.length();
+ }
+ String tag = null;
+ if (event instanceof ScalarEvent) {
+ tag = ((ScalarEvent) event).getTag();
+ } else if (event instanceof CollectionStartEvent) {
+ tag = ((CollectionStartEvent) event).getTag();
+ }
+ if (tag != null) {
+ if (preparedTag == null) {
+ preparedTag = prepareTag(tag);
+ }
+ length += preparedTag.length();
+ }
+ if (event instanceof ScalarEvent) {
+ if (analysis == null) {
+ analysis = analyzeScalar(((ScalarEvent) event).getValue());
+ }
+ length += analysis.getScalar().length();
+ }
+ return length < maxSimpleKeyLength && (event instanceof AliasEvent
+ || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline())
+ || checkEmptySequence() || checkEmptyMapping());
+ }
+
+ // Anchor, Tag, and Scalar processors.
+
+ private void processAnchor(String indicator) throws IOException {
+ NodeEvent ev = (NodeEvent) event;
+ if (ev.getAnchor() == null) {
+ preparedAnchor = null;
+ return;
+ }
+ if (preparedAnchor == null) {
+ preparedAnchor = prepareAnchor(ev.getAnchor());
+ }
+ writeIndicator(indicator + preparedAnchor, true, false, false);
+ preparedAnchor = null;
+ }
+
+ private void processTag() throws IOException {
+ String tag = null;
+ if (event instanceof ScalarEvent) {
+ ScalarEvent ev = (ScalarEvent) event;
+ tag = ev.getTag();
+ if (style == null) {
+ style = chooseScalarStyle();
+ }
+ if ((!canonical || tag == null)
+ && ((style == null && ev.getImplicit().canOmitTagInPlainScalar())
+ || (style != null && ev.getImplicit().canOmitTagInNonPlainScalar()))) {
+ preparedTag = null;
+ return;
+ }
+ if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) {
+ tag = "!";
preparedTag = null;
+ }
+ } else {
+ CollectionStartEvent ev = (CollectionStartEvent) event;
+ tag = ev.getTag();
+ if ((!canonical || tag == null) && ev.getImplicit()) {
+ preparedTag = null;
+ return;
+ }
}
-
- private Character chooseScalarStyle() {
- ScalarEvent ev = (ScalarEvent) event;
- if (analysis == null) {
- analysis = analyzeScalar(ev.getValue());
- }
- if (ev.getStyle() != null && ev.getStyle() == '"' || this.canonical) {
- return '"';
- }
- if (ev.getStyle() == null && ev.getImplicit().canOmitTagInPlainScalar()) {
- if (!(simpleKeyContext && (analysis.empty || analysis.multiline))
- && ((flowLevel != 0 && analysis.allowFlowPlain) || (flowLevel == 0 && analysis.allowBlockPlain))) {
- return null;
- }
- }
- if (ev.getStyle() != null && (ev.getStyle() == '|' || ev.getStyle() == '>')) {
- if (flowLevel == 0 && !simpleKeyContext && analysis.allowBlock) {
- return ev.getStyle();
- }
- }
- if (ev.getStyle() == null || ev.getStyle() == '\'') {
- if (analysis.allowSingleQuoted && !(simpleKeyContext && analysis.multiline)) {
- return '\'';
- }
- }
- return '"';
+ if (tag == null) {
+ throw new EmitterException("tag is not specified");
}
-
- private void processScalar() throws IOException {
- ScalarEvent ev = (ScalarEvent) event;
- if (analysis == null) {
- analysis = analyzeScalar(ev.getValue());
- }
- if (style == null) {
- style = chooseScalarStyle();
- }
- boolean split = !simpleKeyContext && splitLines;
- if (style == null) {
- writePlain(analysis.scalar, split);
- } else {
- switch (style) {
- case '"':
- writeDoubleQuoted(analysis.scalar, split);
- break;
- case '\'':
- writeSingleQuoted(analysis.scalar, split);
- break;
- case '>':
- writeFolded(analysis.scalar, split);
- break;
- case '|':
- writeLiteral(analysis.scalar);
- break;
- default:
- throw new YAMLException("Unexpected style: " + style);
- }
- }
- analysis = null;
- style = null;
+ if (preparedTag == null) {
+ preparedTag = prepareTag(tag);
}
+ writeIndicator(preparedTag, true, false, false);
+ preparedTag = null;
+ }
+
+ private DumperOptions.ScalarStyle chooseScalarStyle() {
+ ScalarEvent ev = (ScalarEvent) event;
+ if (analysis == null) {
+ analysis = analyzeScalar(ev.getValue());
+ }
+ if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED
+ || this.canonical) {
+ return DumperOptions.ScalarStyle.DOUBLE_QUOTED;
+ }
+ if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) {
+ if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline()))
+ && ((flowLevel != 0 && analysis.isAllowFlowPlain())
+ || (flowLevel == 0 && analysis.isAllowBlockPlain()))) {
+ return null;
+ }
+ }
+ if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL
+ || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) {
+ if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) {
+ return ev.getScalarStyle();
+ }
+ }
+ if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) {
+ if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) {
+ return DumperOptions.ScalarStyle.SINGLE_QUOTED;
+ }
+ }
+ return DumperOptions.ScalarStyle.DOUBLE_QUOTED;
+ }
- // Analyzers.
-
- private String prepareVersion(Version version) {
- if (version.major() != 1) {
- throw new EmitterException("unsupported YAML version: " + version);
- }
- return version.getRepresentation();
+ private void processScalar() throws IOException {
+ ScalarEvent ev = (ScalarEvent) event;
+ if (analysis == null) {
+ analysis = analyzeScalar(ev.getValue());
+ }
+ if (style == null) {
+ style = chooseScalarStyle();
}
+ boolean split = !simpleKeyContext && splitLines;
+ if (style == null) {
+ writePlain(analysis.getScalar(), split);
+ } else {
+ switch (style) {
+ case DOUBLE_QUOTED:
+ writeDoubleQuoted(analysis.getScalar(), split);
+ break;
+ case SINGLE_QUOTED:
+ writeSingleQuoted(analysis.getScalar(), split);
+ break;
+ case FOLDED:
+ writeFolded(analysis.getScalar(), split);
+ break;
+ case LITERAL:
+ writeLiteral(analysis.getScalar());
+ break;
+ default:
+ throw new YAMLException("Unexpected style: " + style);
+ }
+ }
+ analysis = null;
+ style = null;
+ }
- private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$");
+ // Analyzers.
- private String prepareTagHandle(String handle) {
- if (handle.length() == 0) {
- throw new EmitterException("tag handle must not be empty");
- } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') {
- throw new EmitterException("tag handle must start and end with '!': " + handle);
- } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) {
- throw new EmitterException("invalid character in the tag handle: " + handle);
- }
- return handle;
+ private String prepareVersion(Version version) {
+ if (version.major() != 1) {
+ throw new EmitterException("unsupported YAML version: " + version);
+ }
+ return version.getRepresentation();
+ }
+
+ private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$");
+
+ private String prepareTagHandle(String handle) {
+ if (handle.length() == 0) {
+ throw new EmitterException("tag handle must not be empty");
+ } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') {
+ throw new EmitterException("tag handle must start and end with '!': " + handle);
+ } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) {
+ throw new EmitterException("invalid character in the tag handle: " + handle);
}
+ return handle;
+ }
- private String prepareTagPrefix(String prefix) {
- if (prefix.length() == 0) {
- throw new EmitterException("tag prefix must not be empty");
- }
- StringBuilder chunks = new StringBuilder();
- int start = 0;
- int end = 0;
- if (prefix.charAt(0) == '!') {
- end = 1;
- }
- while (end < prefix.length()) {
- end++;
- }
- if (start < end) {
- chunks.append(prefix.substring(start, end));
- }
- return chunks.toString();
+ private String prepareTagPrefix(String prefix) {
+ if (prefix.length() == 0) {
+ throw new EmitterException("tag prefix must not be empty");
+ }
+ StringBuilder chunks = new StringBuilder();
+ int start = 0;
+ int end = 0;
+ if (prefix.charAt(0) == '!') {
+ end = 1;
+ }
+ while (end < prefix.length()) {
+ end++;
}
+ if (start < end) {
+ chunks.append(prefix, start, end);
+ }
+ return chunks.toString();
+ }
- private String prepareTag(String tag) {
- if (tag.length() == 0) {
- throw new EmitterException("tag must not be empty");
- }
- if ("!".equals(tag)) {
- return tag;
- }
- String handle = null;
- String suffix = tag;
- // shall the tag prefixes be sorted as in PyYAML?
- for (String prefix : tagPrefixes.keySet()) {
- if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) {
- handle = prefix;
- }
- }
- if (handle != null) {
- suffix = tag.substring(handle.length());
- handle = tagPrefixes.get(handle);
- }
+ private String prepareTag(String tag) {
+ if (tag.length() == 0) {
+ throw new EmitterException("tag must not be empty");
+ }
+ if ("!".equals(tag)) {
+ return tag;
+ }
+ String handle = null;
+ String suffix = tag;
+ // shall the tag prefixes be sorted as in PyYAML?
+ for (String prefix : tagPrefixes.keySet()) {
+ if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) {
+ handle = prefix;
+ }
+ }
+ if (handle != null) {
+ suffix = tag.substring(handle.length());
+ handle = tagPrefixes.get(handle);
+ }
- int end = suffix.length();
- String suffixText = end > 0 ? suffix.substring(0, end) : "";
+ int end = suffix.length();
+ String suffixText = end > 0 ? suffix.substring(0, end) : "";
- if (handle != null) {
- return handle + suffixText;
- }
- return "!<" + suffixText + ">";
+ if (handle != null) {
+ return handle + suffixText;
}
+ return "!<" + suffixText + ">";
+ }
- private final static Pattern ANCHOR_FORMAT = Pattern.compile("^[-_\\w]*$");
-
- static String prepareAnchor(String anchor) {
- if (anchor.length() == 0) {
- throw new EmitterException("anchor must not be empty");
- }
- if (!ANCHOR_FORMAT.matcher(anchor).matches()) {
- throw new EmitterException("invalid character in the anchor: " + anchor);
- }
- return anchor;
+ static String prepareAnchor(String anchor) {
+ if (anchor.length() == 0) {
+ throw new EmitterException("anchor must not be empty");
}
+ for (Character invalid : INVALID_ANCHOR) {
+ if (anchor.indexOf(invalid) > -1) {
+ throw new EmitterException("Invalid character '" + invalid + "' in the anchor: " + anchor);
+ }
+ }
+ Matcher matcher = SPACES_PATTERN.matcher(anchor);
+ if (matcher.find()) {
+ throw new EmitterException("Anchor may not contain spaces: " + anchor);
+ }
+ return anchor;
+ }
- private ScalarAnalysis analyzeScalar(String scalar) {
- // Empty scalar is a special case.
- if (scalar.length() == 0) {
- return new ScalarAnalysis(scalar, true, false, false, true, true, false);
- }
- // Indicators and special characters.
- boolean blockIndicators = false;
- boolean flowIndicators = false;
- boolean lineBreaks = false;
- boolean specialCharacters = false;
-
- // Important whitespace combinations.
- boolean leadingSpace = false;
- boolean leadingBreak = false;
- boolean trailingSpace = false;
- boolean trailingBreak = false;
- boolean breakSpace = false;
- boolean spaceBreak = false;
-
- // Check document indicators.
- if (scalar.startsWith("---") || scalar.startsWith("...")) {
+ private ScalarAnalysis analyzeScalar(String scalar) {
+ // Empty scalar is a special case.
+ if (scalar.length() == 0) {
+ return new ScalarAnalysis(scalar, true, false, false, true, true, false);
+ }
+ // Indicators and special characters.
+ boolean blockIndicators = false;
+ boolean flowIndicators = false;
+ boolean lineBreaks = false;
+ boolean specialCharacters = false;
+
+ // Important whitespace combinations.
+ boolean leadingSpace = false;
+ boolean leadingBreak = false;
+ boolean trailingSpace = false;
+ boolean trailingBreak = false;
+ boolean breakSpace = false;
+ boolean spaceBreak = false;
+
+ // Check document indicators.
+ if (scalar.startsWith("---") || scalar.startsWith("...")) {
+ blockIndicators = true;
+ flowIndicators = true;
+ }
+ // First character or preceded by a whitespace.
+ boolean preceededByWhitespace = true;
+ boolean followedByWhitespace =
+ scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1));
+ // The previous character is a space.
+ boolean previousSpace = false;
+
+ // The previous character is a break.
+ boolean previousBreak = false;
+
+ int index = 0;
+
+ while (index < scalar.length()) {
+ int c = scalar.codePointAt(index);
+ // Check for indicators.
+ if (index == 0) {
+ // Leading indicators are special characters.
+ if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) {
+ flowIndicators = true;
+ blockIndicators = true;
+ }
+ if (c == '?' || c == ':') {
+ flowIndicators = true;
+ if (followedByWhitespace) {
blockIndicators = true;
- flowIndicators = true;
+ }
}
- // First character or preceded by a whitespace.
- boolean preceededByWhitespace = true;
- boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.charAt(1));
- // The previous character is a space.
- boolean previousSpace = false;
-
- // The previous character is a break.
- boolean previousBreak = false;
-
- int index = 0;
-
- while (index < scalar.length()) {
- char ch = scalar.charAt(index);
- // Check for indicators.
- if (index == 0) {
- // Leading indicators are special characters.
- if ("#,[]{}&*!|>\'\"%@`".indexOf(ch) != -1) {
- flowIndicators = true;
- blockIndicators = true;
- }
- if (ch == '?' || ch == ':') {
- flowIndicators = true;
- if (followedByWhitespace) {
- blockIndicators = true;
- }
- }
- if (ch == '-' && followedByWhitespace) {
- flowIndicators = true;
- blockIndicators = true;
- }
- } else {
- // Some indicators cannot appear within a scalar as well.
- if (",?[]{}".indexOf(ch) != -1) {
- flowIndicators = true;
- }
- if (ch == ':') {
- flowIndicators = true;
- if (followedByWhitespace) {
- blockIndicators = true;
- }
- }
- if (ch == '#' && preceededByWhitespace) {
- flowIndicators = true;
- blockIndicators = true;
- }
- }
- // Check for line breaks, special, and unicode characters.
- boolean isLineBreak = Constant.LINEBR.has(ch);
- if (isLineBreak) {
- lineBreaks = true;
- }
- if (!(ch == '\n' || ('\u0020' <= ch && ch <= '\u007E'))) {
- if ((ch == '\u0085' || ('\u00A0' <= ch && ch <= '\uD7FF') || ('\uE000' <= ch && ch <= '\uFFFD'))
- && (ch != '\uFEFF')) {
- // unicode is used
- if (!this.allowUnicode) {
- specialCharacters = true;
- }
- } else {
- specialCharacters = true;
- }
- }
- // Detect important whitespace combinations.
- if (ch == ' ') {
- if (index == 0) {
- leadingSpace = true;
- }
- if (index == scalar.length() - 1) {
- trailingSpace = true;
- }
- if (previousBreak) {
- breakSpace = true;
- }
- previousSpace = true;
- previousBreak = false;
- } else if (isLineBreak) {
- if (index == 0) {
- leadingBreak = true;
- }
- if (index == scalar.length() - 1) {
- trailingBreak = true;
- }
- if (previousSpace) {
- spaceBreak = true;
- }
- previousSpace = false;
- previousBreak = true;
- } else {
- previousSpace = false;
- previousBreak = false;
- }
-
- // Prepare for the next character.
- index++;
- preceededByWhitespace = Constant.NULL_BL_T.has(ch) || isLineBreak;
- followedByWhitespace = index + 1 >= scalar.length()
- || (Constant.NULL_BL_T.has(scalar.charAt(index + 1))) || isLineBreak;
- }
- // Let's decide what styles are allowed.
- boolean allowFlowPlain = true;
- boolean allowBlockPlain = true;
- boolean allowSingleQuoted = true;
- boolean allowBlock = true;
- // Leading and trailing whitespaces are bad for plain scalars.
- if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
- allowFlowPlain = allowBlockPlain = false;
- }
- // We do not permit trailing spaces for block scalars.
- if (trailingSpace) {
- allowBlock = false;
- }
- // Spaces at the beginning of a new line are only acceptable for block
- // scalars.
- if (breakSpace) {
- allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
- }
- // Spaces followed by breaks, as well as special character are only
- // allowed for double quoted scalars.
- if (spaceBreak || specialCharacters) {
- allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
- }
- // Although the plain scalar writer supports breaks, we never emit
- // multiline plain scalars in the flow context.
- if (lineBreaks) {
- allowFlowPlain = false;
- }
- // Flow indicators are forbidden for flow plain scalars.
- if (flowIndicators) {
- allowFlowPlain = false;
+ if (c == '-' && followedByWhitespace) {
+ flowIndicators = true;
+ blockIndicators = true;
}
- // Block indicators are forbidden for block plain scalars.
- if (blockIndicators) {
- allowBlockPlain = false;
+ } else {
+ // Some indicators cannot appear within a scalar as well.
+ if (",?[]{}".indexOf(c) != -1) {
+ flowIndicators = true;
}
-
- return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain,
- allowSingleQuoted, allowBlock);
+ if (c == ':') {
+ flowIndicators = true;
+ if (followedByWhitespace) {
+ blockIndicators = true;
+ }
+ }
+ if (c == '#' && preceededByWhitespace) {
+ flowIndicators = true;
+ blockIndicators = true;
+ }
+ }
+ // Check for line breaks, special, and unicode characters.
+ boolean isLineBreak = Constant.LINEBR.has(c);
+ if (isLineBreak) {
+ lineBreaks = true;
+ }
+ if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) {
+ if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD)
+ || (c >= 0x10000 && c <= 0x10FFFF)) {
+ // unicode is used
+ if (!this.allowUnicode) {
+ specialCharacters = true;
+ }
+ } else {
+ specialCharacters = true;
+ }
+ }
+ // Detect important whitespace combinations.
+ if (c == ' ') {
+ if (index == 0) {
+ leadingSpace = true;
+ }
+ if (index == scalar.length() - 1) {
+ trailingSpace = true;
+ }
+ if (previousBreak) {
+ breakSpace = true;
+ }
+ previousSpace = true;
+ previousBreak = false;
+ } else if (isLineBreak) {
+ if (index == 0) {
+ leadingBreak = true;
+ }
+ if (index == scalar.length() - 1) {
+ trailingBreak = true;
+ }
+ if (previousSpace) {
+ spaceBreak = true;
+ }
+ previousSpace = false;
+ previousBreak = true;
+ } else {
+ previousSpace = false;
+ previousBreak = false;
+ }
+
+ // Prepare for the next character.
+ index += Character.charCount(c);
+ preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak;
+ followedByWhitespace = true;
+ if (index + 1 < scalar.length()) {
+ int nextIndex = index + Character.charCount(scalar.codePointAt(index));
+ if (nextIndex < scalar.length()) {
+ followedByWhitespace =
+ (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak;
+ }
+ }
+ }
+ // Let's decide what styles are allowed.
+ boolean allowFlowPlain = true;
+ boolean allowBlockPlain = true;
+ boolean allowSingleQuoted = true;
+ boolean allowBlock = true;
+ // Leading and trailing whitespaces are bad for plain scalars.
+ if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
+ allowFlowPlain = allowBlockPlain = false;
+ }
+ // We do not permit trailing spaces for block scalars.
+ if (trailingSpace) {
+ allowBlock = false;
+ }
+ // Spaces at the beginning of a new line are only acceptable for block
+ // scalars.
+ if (breakSpace) {
+ allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
+ }
+ // Spaces followed by breaks, as well as special character are only
+ // allowed for double quoted scalars.
+ if (spaceBreak || specialCharacters) {
+ allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
+ }
+ // Although the plain scalar writer supports breaks, we never emit
+ // multiline plain scalars in the flow context.
+ if (lineBreaks) {
+ allowFlowPlain = false;
+ }
+ // Flow indicators are forbidden for flow plain scalars.
+ if (flowIndicators) {
+ allowFlowPlain = false;
+ }
+ // Block indicators are forbidden for block plain scalars.
+ if (blockIndicators) {
+ allowBlockPlain = false;
}
- // Writers.
+ return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain,
+ allowSingleQuoted, allowBlock);
+ }
- void flushStream() throws IOException {
- stream.flush();
- }
+ // Writers.
- void writeStreamStart() {
- // BOM is written by Writer.
- }
+ void flushStream() throws IOException {
+ stream.flush();
+ }
- void writeStreamEnd() throws IOException {
- flushStream();
+ void writeStreamStart() {
+ // BOM is written by Writer.
+ }
+
+ void writeStreamEnd() throws IOException {
+ flushStream();
+ }
+
+ void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace,
+ boolean indentation) throws IOException {
+ if (!this.whitespace && needWhitespace) {
+ this.column++;
+ stream.write(SPACE);
+ }
+ this.whitespace = whitespace;
+ this.indention = this.indention && indentation;
+ this.column += indicator.length();
+ openEnded = false;
+ stream.write(indicator);
+ }
+
+ void writeIndent() throws IOException {
+ int indent;
+ if (this.indent != null) {
+ indent = this.indent;
+ } else {
+ indent = 0;
}
- void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace,
- boolean indentation) throws IOException {
- if (!this.whitespace && needWhitespace) {
- this.column++;
- stream.write(SPACE);
- }
- this.whitespace = whitespace;
- this.indention = this.indention && indentation;
- this.column += indicator.length();
- openEnded = false;
- stream.write(indicator);
+ if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) {
+ writeLineBreak(null);
}
- void writeIndent() throws IOException {
- int indent;
- if (this.indent != null) {
- indent = this.indent;
- } else {
- indent = 0;
- }
+ writeWhitespace(indent - this.column);
+ }
- if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) {
+ private void writeWhitespace(int length) throws IOException {
+ if (length <= 0) {
+ return;
+ }
+ this.whitespace = true;
+ char[] data = new char[length];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = ' ';
+ }
+ this.column += length;
+ stream.write(data);
+ }
+
+ private void writeLineBreak(String data) throws IOException {
+ this.whitespace = true;
+ this.indention = true;
+ this.column = 0;
+ if (data == null) {
+ stream.write(this.bestLineBreak);
+ } else {
+ stream.write(data);
+ }
+ }
+
+ void writeVersionDirective(String versionText) throws IOException {
+ stream.write("%YAML ");
+ stream.write(versionText);
+ writeLineBreak(null);
+ }
+
+ void writeTagDirective(String handleText, String prefixText) throws IOException {
+ // XXX: not sure 4 invocations better then StringBuilders created by str
+ // + str
+ stream.write("%TAG ");
+ stream.write(handleText);
+ stream.write(SPACE);
+ stream.write(prefixText);
+ writeLineBreak(null);
+ }
+
+ // Scalar streams.
+ private void writeSingleQuoted(String text, boolean split) throws IOException {
+ writeIndicator("'", true, false, false);
+ boolean spaces = false;
+ boolean breaks = false;
+ int start = 0, end = 0;
+ char ch;
+ while (end <= text.length()) {
+ ch = 0;
+ if (end < text.length()) {
+ ch = text.charAt(end);
+ }
+ if (spaces) {
+ if (ch == 0 || ch != ' ') {
+ if (start + 1 == end && this.column > this.bestWidth && split && start != 0
+ && end != text.length()) {
+ writeIndent();
+ } else {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ }
+ start = end;
+ }
+ } else if (breaks) {
+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
+ if (text.charAt(start) == '\n') {
writeLineBreak(null);
- }
-
- writeWhitespace(indent - this.column);
+ }
+ String data = text.substring(start, end);
+ for (char br : data.toCharArray()) {
+ if (br == '\n') {
+ writeLineBreak(null);
+ } else {
+ writeLineBreak(String.valueOf(br));
+ }
+ }
+ writeIndent();
+ start = end;
+ }
+ } else {
+ if (Constant.LINEBR.has(ch, "\0 '")) {
+ if (start < end) {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ start = end;
+ }
+ }
+ }
+ if (ch == '\'') {
+ this.column += 2;
+ stream.write("''");
+ start = end + 1;
+ }
+ if (ch != 0) {
+ spaces = ch == ' ';
+ breaks = Constant.LINEBR.has(ch);
+ }
+ end++;
}
+ writeIndicator("'", false, false, false);
+ }
+
+ private void writeDoubleQuoted(String text, boolean split) throws IOException {
+ writeIndicator("\"", true, false, false);
+ int start = 0;
+ int end = 0;
+ while (end <= text.length()) {
+ Character ch = null;
+ if (end < text.length()) {
+ ch = text.charAt(end);
+ }
+ if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1
+ || !('\u0020' <= ch && ch <= '\u007E')) {
+ if (start < end) {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ start = end;
+ }
+ if (ch != null) {
+ String data;
+
+ if (ESCAPE_REPLACEMENTS.containsKey(ch)) {
+ data = "\\" + ESCAPE_REPLACEMENTS.get(ch);
+ } else {
+ int codePoint;
+
+ if (Character.isHighSurrogate(ch) && end + 1 < text.length()) {
+ char ch2 = text.charAt(end + 1);
+ codePoint = Character.toCodePoint(ch, ch2);
+ } else {
+ codePoint = ch;
+ }
+
+ if (this.allowUnicode && StreamReader.isPrintable(codePoint)) {
+ data = String.valueOf(Character.toChars(codePoint));
- private void writeWhitespace(int length) throws IOException {
- if (length <= 0) {
- return;
+ if (Character.charCount(codePoint) == 2) {
+ end++;
+ }
+ } else {
+ // if !allowUnicode or the character is not printable,
+ // we must encode it
+ if (ch <= '\u00FF') {
+ String s = "0" + Integer.toString(ch, 16);
+ data = "\\x" + s.substring(s.length() - 2);
+ } else if (Character.charCount(codePoint) == 2) {
+ end++;
+ String s = "000" + Long.toHexString(codePoint);
+ data = "\\U" + s.substring(s.length() - 8);
+ } else {
+ String s = "000" + Integer.toString(ch, 16);
+ data = "\\u" + s.substring(s.length() - 4);
+ }
+ }
+ }
+
+ this.column += data.length();
+ stream.write(data);
+ start = end + 1;
+ }
+ }
+ if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end)
+ && (this.column + (end - start)) > this.bestWidth && split) {
+ String data;
+ if (start >= end) {
+ data = "\\";
+ } else {
+ data = text.substring(start, end) + "\\";
}
- this.whitespace = true;
- char[] data = new char[length];
- for (int i = 0; i < data.length; i++) {
- data[i] = ' ';
+ if (start < end) {
+ start = end;
}
- this.column += length;
+ this.column += data.length();
stream.write(data);
+ writeIndent();
+ this.whitespace = false;
+ this.indention = false;
+ if (text.charAt(start) == ' ') {
+ data = "\\";
+ this.column += data.length();
+ stream.write(data);
+ }
+ }
+ end += 1;
}
-
- private void writeLineBreak(String data) throws IOException {
- this.whitespace = true;
- this.indention = true;
- this.column = 0;
- if (data == null) {
- stream.write(this.bestLineBreak);
+ writeIndicator("\"", false, false, false);
+ }
+
+ private boolean writeCommentLines(List<CommentLine> commentLines) throws IOException {
+ boolean wroteComment = false;
+ if (emitComments) {
+ int indentColumns = 0;
+ boolean firstComment = true;
+ for (CommentLine commentLine : commentLines) {
+ if (commentLine.getCommentType() != CommentType.BLANK_LINE) {
+ if (firstComment) {
+ firstComment = false;
+ writeIndicator("#", commentLine.getCommentType() == CommentType.IN_LINE, false, false);
+ indentColumns = this.column > 0 ? this.column - 1 : 0;
+ } else {
+ writeWhitespace(indentColumns);
+ writeIndicator("#", false, false, false);
+ }
+ stream.write(commentLine.getValue());
+ writeLineBreak(null);
} else {
- stream.write(data);
+ writeLineBreak(null);
+ writeIndent();
}
+ wroteComment = true;
+ }
}
+ return wroteComment;
+ }
- void writeVersionDirective(String versionText) throws IOException {
- stream.write("%YAML ");
- stream.write(versionText);
- writeLineBreak(null);
- }
-
- void writeTagDirective(String handleText, String prefixText) throws IOException {
- // XXX: not sure 4 invocations better then StringBuilders created by str
- // + str
- stream.write("%TAG ");
- stream.write(handleText);
- stream.write(SPACE);
- stream.write(prefixText);
- writeLineBreak(null);
- }
-
- // Scalar streams.
- private void writeSingleQuoted(String text, boolean split) throws IOException {
- writeIndicator("'", true, false, false);
- boolean spaces = false;
- boolean breaks = false;
- int start = 0, end = 0;
- char ch;
- while (end <= text.length()) {
- ch = 0;
- if (end < text.length()) {
- ch = text.charAt(end);
- }
- if (spaces) {
- if (ch == 0 || ch != ' ') {
- if (start + 1 == end && this.column > this.bestWidth && split && start != 0
- && end != text.length()) {
- writeIndent();
- } else {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- }
- start = end;
- }
- } else if (breaks) {
- if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
- if (text.charAt(start) == '\n') {
- writeLineBreak(null);
- }
- String data = text.substring(start, end);
- for (char br : data.toCharArray()) {
- if (br == '\n') {
- writeLineBreak(null);
- } else {
- writeLineBreak(String.valueOf(br));
- }
- }
- writeIndent();
- start = end;
- }
- } else {
- if (Constant.LINEBR.has(ch, "\0 \'")) {
- if (start < end) {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- start = end;
- }
- }
- }
- if (ch == '\'') {
- this.column += 2;
- stream.write("''");
- start = end + 1;
- }
- if (ch != 0) {
- spaces = ch == ' ';
- breaks = Constant.LINEBR.has(ch);
- }
- end++;
- }
- writeIndicator("'", false, false, false);
+ private void writeBlockComment() throws IOException {
+ if (!blockCommentsCollector.isEmpty()) {
+ writeIndent();
+ writeCommentLines(blockCommentsCollector.consume());
}
+ }
- private void writeDoubleQuoted(String text, boolean split) throws IOException {
- writeIndicator("\"", true, false, false);
- int start = 0;
- int end = 0;
- while (end <= text.length()) {
- Character ch = null;
- if (end < text.length()) {
- ch = text.charAt(end);
- }
- if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1
- || !('\u0020' <= ch && ch <= '\u007E')) {
- if (start < end) {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- start = end;
- }
- if (ch != null) {
- String data;
- if (ESCAPE_REPLACEMENTS.containsKey(ch)) {
- data = "\\" + ESCAPE_REPLACEMENTS.get(ch);
- } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) {
- // if !allowUnicode or the character is not printable,
- // we must encode it
- if (ch <= '\u00FF') {
- String s = "0" + Integer.toString(ch, 16);
- data = "\\x" + s.substring(s.length() - 2);
- } else if (ch >= '\uD800' && ch <= '\uDBFF') {
- if (end + 1 < text.length()) {
- Character ch2 = text.charAt(++end);
- String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2));
- data = "\\U" + s.substring(s.length() - 8);
- } else {
- String s = "000" + Integer.toString(ch, 16);
- data = "\\u" + s.substring(s.length() - 4);
- }
- } else {
- String s = "000" + Integer.toString(ch, 16);
- data = "\\u" + s.substring(s.length() - 4);
- }
- } else {
- data = String.valueOf(ch);
- }
- this.column += data.length();
- stream.write(data);
- start = end + 1;
- }
- }
- if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end)
- && (this.column + (end - start)) > this.bestWidth && split) {
- String data;
- if (start >= end) {
- data = "\\";
- } else {
- data = text.substring(start, end) + "\\";
- }
- if (start < end) {
- start = end;
- }
- this.column += data.length();
- stream.write(data);
- writeIndent();
- this.whitespace = false;
- this.indention = false;
- if (text.charAt(start) == ' ') {
- data = "\\";
- this.column += data.length();
- stream.write(data);
- }
- }
- end += 1;
- }
- writeIndicator("\"", false, false, false);
- }
+ private boolean writeInlineComments() throws IOException {
+ return writeCommentLines(inlineCommentsCollector.consume());
+ }
- private String determineBlockHints(String text) {
- StringBuilder hints = new StringBuilder();
- if (Constant.LINEBR.has(text.charAt(0), " ")) {
- hints.append(bestIndent);
- }
- char ch1 = text.charAt(text.length() - 1);
- if (Constant.LINEBR.hasNo(ch1)) {
- hints.append("-");
- } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) {
- hints.append("+");
- }
- return hints.toString();
+ private String determineBlockHints(String text) {
+ StringBuilder hints = new StringBuilder();
+ if (Constant.LINEBR.has(text.charAt(0), " ")) {
+ hints.append(bestIndent);
}
-
- void writeFolded(String text, boolean split) throws IOException {
- String hints = determineBlockHints(text);
- writeIndicator(">" + hints, true, false, false);
- if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) {
- openEnded = true;
- }
- writeLineBreak(null);
- boolean leadingSpace = true;
- boolean spaces = false;
- boolean breaks = true;
- int start = 0, end = 0;
- while (end <= text.length()) {
- char ch = 0;
- if (end < text.length()) {
- ch = text.charAt(end);
- }
- if (breaks) {
- if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
- if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') {
- writeLineBreak(null);
- }
- leadingSpace = ch == ' ';
- String data = text.substring(start, end);
- for (char br : data.toCharArray()) {
- if (br == '\n') {
- writeLineBreak(null);
- } else {
- writeLineBreak(String.valueOf(br));
- }
- }
- if (ch != 0) {
- writeIndent();
- }
- start = end;
- }
- } else if (spaces) {
- if (ch != ' ') {
- if (start + 1 == end && this.column > this.bestWidth && split) {
- writeIndent();
- } else {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- }
- start = end;
- }
+ char ch1 = text.charAt(text.length() - 1);
+ if (Constant.LINEBR.hasNo(ch1)) {
+ hints.append("-");
+ } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) {
+ hints.append("+");
+ }
+ return hints.toString();
+ }
+
+ void writeFolded(String text, boolean split) throws IOException {
+ String hints = determineBlockHints(text);
+ writeIndicator(">" + hints, true, false, false);
+ if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) {
+ openEnded = true;
+ }
+ if (!writeInlineComments()) {
+ writeLineBreak(null);
+ }
+ boolean leadingSpace = true;
+ boolean spaces = false;
+ boolean breaks = true;
+ int start = 0, end = 0;
+ while (end <= text.length()) {
+ char ch = 0;
+ if (end < text.length()) {
+ ch = text.charAt(end);
+ }
+ if (breaks) {
+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
+ if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') {
+ writeLineBreak(null);
+ }
+ leadingSpace = ch == ' ';
+ String data = text.substring(start, end);
+ for (char br : data.toCharArray()) {
+ if (br == '\n') {
+ writeLineBreak(null);
} else {
- if (Constant.LINEBR.has(ch, "\0 ")) {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- if (ch == 0) {
- writeLineBreak(null);
- }
- start = end;
- }
+ writeLineBreak(String.valueOf(br));
}
- if (ch != 0) {
- breaks = Constant.LINEBR.has(ch);
- spaces = ch == ' ';
- }
- end++;
+ }
+ if (ch != 0) {
+ writeIndent();
+ }
+ start = end;
}
+ } else if (spaces) {
+ if (ch != ' ') {
+ if (start + 1 == end && this.column > this.bestWidth && split) {
+ writeIndent();
+ } else {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ }
+ start = end;
+ }
+ } else {
+ if (Constant.LINEBR.has(ch, "\0 ")) {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ if (ch == 0) {
+ writeLineBreak(null);
+ }
+ start = end;
+ }
+ }
+ if (ch != 0) {
+ breaks = Constant.LINEBR.has(ch);
+ spaces = ch == ' ';
+ }
+ end++;
}
+ }
- void writeLiteral(String text) throws IOException {
- String hints = determineBlockHints(text);
- writeIndicator("|" + hints, true, false, false);
- if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') {
- openEnded = true;
- }
- writeLineBreak(null);
- boolean breaks = true;
- int start = 0, end = 0;
- while (end <= text.length()) {
- char ch = 0;
- if (end < text.length()) {
- ch = text.charAt(end);
- }
- if (breaks) {
- if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
- String data = text.substring(start, end);
- for (char br : data.toCharArray()) {
- if (br == '\n') {
- writeLineBreak(null);
- } else {
- writeLineBreak(String.valueOf(br));
- }
- }
- if (ch != 0) {
- writeIndent();
- }
- start = end;
- }
+ void writeLiteral(String text) throws IOException {
+ String hints = determineBlockHints(text);
+ writeIndicator("|" + hints, true, false, false);
+ if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') {
+ openEnded = true;
+ }
+ if (!writeInlineComments()) {
+ writeLineBreak(null);
+ }
+ boolean breaks = true;
+ int start = 0, end = 0;
+ while (end <= text.length()) {
+ char ch = 0;
+ if (end < text.length()) {
+ ch = text.charAt(end);
+ }
+ if (breaks) {
+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
+ String data = text.substring(start, end);
+ for (char br : data.toCharArray()) {
+ if (br == '\n') {
+ writeLineBreak(null);
} else {
- if (ch == 0 || Constant.LINEBR.has(ch)) {
- stream.write(text, start, end - start);
- if (ch == 0) {
- writeLineBreak(null);
- }
- start = end;
- }
- }
- if (ch != 0) {
- breaks = Constant.LINEBR.has(ch);
+ writeLineBreak(String.valueOf(br));
}
- end++;
+ }
+ if (ch != 0) {
+ writeIndent();
+ }
+ start = end;
}
+ } else {
+ if (ch == 0 || Constant.LINEBR.has(ch)) {
+ stream.write(text, start, end - start);
+ if (ch == 0) {
+ writeLineBreak(null);
+ }
+ start = end;
+ }
+ }
+ if (ch != 0) {
+ breaks = Constant.LINEBR.has(ch);
+ }
+ end++;
}
+ }
- void writePlain(String text, boolean split) throws IOException {
- if (rootContext) {
- openEnded = true;
- }
- if (text.length() == 0) {
- return;
- }
- if (!this.whitespace) {
- this.column++;
- stream.write(SPACE);
- }
- this.whitespace = false;
- this.indention = false;
- boolean spaces = false;
- boolean breaks = false;
- int start = 0, end = 0;
- while (end <= text.length()) {
- char ch = 0;
- if (end < text.length()) {
- ch = text.charAt(end);
- }
- if (spaces) {
- if (ch != ' ') {
- if (start + 1 == end && this.column > this.bestWidth && split) {
- writeIndent();
- this.whitespace = false;
- this.indention = false;
- } else {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- }
- start = end;
- }
- } else if (breaks) {
- if (Constant.LINEBR.hasNo(ch)) {
- if (text.charAt(start) == '\n') {
- writeLineBreak(null);
- }
- String data = text.substring(start, end);
- for (char br : data.toCharArray()) {
- if (br == '\n') {
- writeLineBreak(null);
- } else {
- writeLineBreak(String.valueOf(br));
- }
- }
- writeIndent();
- this.whitespace = false;
- this.indention = false;
- start = end;
- }
+ void writePlain(String text, boolean split) throws IOException {
+ if (rootContext) {
+ openEnded = true;
+ }
+ if (text.length() == 0) {
+ return;
+ }
+ if (!this.whitespace) {
+ this.column++;
+ stream.write(SPACE);
+ }
+ this.whitespace = false;
+ this.indention = false;
+ boolean spaces = false;
+ boolean breaks = false;
+ int start = 0, end = 0;
+ while (end <= text.length()) {
+ char ch = 0;
+ if (end < text.length()) {
+ ch = text.charAt(end);
+ }
+ if (spaces) {
+ if (ch != ' ') {
+ if (start + 1 == end && this.column > this.bestWidth && split) {
+ writeIndent();
+ this.whitespace = false;
+ this.indention = false;
+ } else {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ }
+ start = end;
+ }
+ } else if (breaks) {
+ if (Constant.LINEBR.hasNo(ch)) {
+ if (text.charAt(start) == '\n') {
+ writeLineBreak(null);
+ }
+ String data = text.substring(start, end);
+ for (char br : data.toCharArray()) {
+ if (br == '\n') {
+ writeLineBreak(null);
} else {
- if (ch == 0 || Constant.LINEBR.has(ch)) {
- int len = end - start;
- this.column += len;
- stream.write(text, start, len);
- start = end;
- }
+ writeLineBreak(String.valueOf(br));
}
- if (ch != 0) {
- spaces = ch == ' ';
- breaks = Constant.LINEBR.has(ch);
- }
- end++;
- }
+ }
+ writeIndent();
+ this.whitespace = false;
+ this.indention = false;
+ start = end;
+ }
+ } else {
+ if (Constant.LINEBR.has(ch, "\0 ")) {
+ int len = end - start;
+ this.column += len;
+ stream.write(text, start, len);
+ start = end;
+ }
+ }
+ if (ch != 0) {
+ spaces = ch == ' ';
+ breaks = Constant.LINEBR.has(ch);
+ }
+ end++;
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/emitter/EmitterException.java b/src/main/java/org/yaml/snakeyaml/emitter/EmitterException.java
index ed83fee5..b029f3be 100644
--- a/src/main/java/org/yaml/snakeyaml/emitter/EmitterException.java
+++ b/src/main/java/org/yaml/snakeyaml/emitter/EmitterException.java
@@ -1,26 +1,25 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.emitter;
import org.yaml.snakeyaml.error.YAMLException;
public class EmitterException extends YAMLException {
- private static final long serialVersionUID = -8280070025452995908L;
- public EmitterException(String msg) {
- super(msg);
- }
+ private static final long serialVersionUID = -8280070025452995908L;
+
+ public EmitterException(String msg) {
+ super(msg);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/emitter/EmitterState.java b/src/main/java/org/yaml/snakeyaml/emitter/EmitterState.java
index 8e6622e0..82ca1349 100755
--- a/src/main/java/org/yaml/snakeyaml/emitter/EmitterState.java
+++ b/src/main/java/org/yaml/snakeyaml/emitter/EmitterState.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.emitter;
@@ -21,5 +19,6 @@ import java.io.IOException;
* Python's methods are first class object. Java needs a class.
*/
interface EmitterState {
- void expect() throws IOException;
-} \ No newline at end of file
+
+ void expect() throws IOException;
+}
diff --git a/src/main/java/org/yaml/snakeyaml/emitter/ScalarAnalysis.java b/src/main/java/org/yaml/snakeyaml/emitter/ScalarAnalysis.java
index 99a0829a..f9a84614 100644
--- a/src/main/java/org/yaml/snakeyaml/emitter/ScalarAnalysis.java
+++ b/src/main/java/org/yaml/snakeyaml/emitter/ScalarAnalysis.java
@@ -1,37 +1,68 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.emitter;
+
+/**
+ * Accumulate information to choose the scalar style
+ */
public final class ScalarAnalysis {
- public String scalar;
- public boolean empty;
- public boolean multiline;
- public boolean allowFlowPlain;
- public boolean allowBlockPlain;
- public boolean allowSingleQuoted;
- public boolean allowBlock;
-
- public ScalarAnalysis(String scalar, boolean empty, boolean multiline, boolean allowFlowPlain,
- boolean allowBlockPlain, boolean allowSingleQuoted, boolean allowBlock) {
- this.scalar = scalar;
- this.empty = empty;
- this.multiline = multiline;
- this.allowFlowPlain = allowFlowPlain;
- this.allowBlockPlain = allowBlockPlain;
- this.allowSingleQuoted = allowSingleQuoted;
- this.allowBlock = allowBlock;
- }
-} \ No newline at end of file
+
+ private final String scalar;
+ private final boolean empty;
+ private final boolean multiline;
+ private final boolean allowFlowPlain;
+ private final boolean allowBlockPlain;
+ private final boolean allowSingleQuoted;
+ private final boolean allowBlock;
+
+ public ScalarAnalysis(String scalar, boolean empty, boolean multiline, boolean allowFlowPlain,
+ boolean allowBlockPlain, boolean allowSingleQuoted, boolean allowBlock) {
+ this.scalar = scalar;
+ this.empty = empty;
+ this.multiline = multiline;
+ this.allowFlowPlain = allowFlowPlain;
+ this.allowBlockPlain = allowBlockPlain;
+ this.allowSingleQuoted = allowSingleQuoted;
+ this.allowBlock = allowBlock;
+ }
+
+ public String getScalar() {
+ return scalar;
+ }
+
+ public boolean isEmpty() {
+ return empty;
+ }
+
+ public boolean isMultiline() {
+ return multiline;
+ }
+
+ public boolean isAllowFlowPlain() {
+ return allowFlowPlain;
+ }
+
+ public boolean isAllowBlockPlain() {
+ return allowBlockPlain;
+ }
+
+ public boolean isAllowSingleQuoted() {
+ return allowSingleQuoted;
+ }
+
+ public boolean isAllowBlock() {
+ return allowBlock;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/env/EnvScalarConstructor.java b/src/main/java/org/yaml/snakeyaml/env/EnvScalarConstructor.java
new file mode 100644
index 00000000..566f6c51
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/env/EnvScalarConstructor.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.env;
+
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.TypeDescription;
+import org.yaml.snakeyaml.constructor.AbstractConstruct;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.error.MissingEnvironmentVariableException;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.Tag;
+
+/**
+ * Construct scalar for format ${VARIABLE} replacing the template with the value from environment.
+ * It can also be used to create JavaBeans when the all the arguments are provided.
+ *
+ * @see <a href="https://bitbucket.org/snakeyaml/snakeyaml/wiki/Variable%20substitution">Variable
+ * substitution</a>
+ * @see <a href="https://docs.docker.com/compose/compose-file/#variable-substitution">Variable
+ * substitution</a>
+ */
+public class EnvScalarConstructor extends Constructor {
+
+ public static final Tag ENV_TAG = new Tag("!ENV");
+ // name must be a word -> \w+
+ // value can be any non-space -> \S+
+ public static final Pattern ENV_FORMAT = Pattern
+ .compile("^\\$\\{\\s*((?<name>\\w+)((?<separator>:?(-|\\?))(?<value>\\S+)?)?)\\s*\\}$");
+
+ /**
+ * For simple cases when no JavaBeans are needed
+ */
+ public EnvScalarConstructor() {
+ this.yamlConstructors.put(ENV_TAG, new ConstructEnv());
+ }
+
+ /**
+ * Create EnvScalarConstructor which can create JavaBeans with variable substitution
+ *
+ * @param theRoot - the class (usually JavaBean) to be constructed
+ * @param moreTDs - collection of classes used by the root class
+ * @param loadingConfig - configuration
+ */
+ public EnvScalarConstructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs,
+ LoaderOptions loadingConfig) {
+ super(theRoot, moreTDs, loadingConfig);
+ this.yamlConstructors.put(ENV_TAG, new ConstructEnv());
+ }
+
+ private class ConstructEnv extends AbstractConstruct {
+
+ public Object construct(Node node) {
+ String val = constructScalar((ScalarNode) node);
+ Matcher matcher = ENV_FORMAT.matcher(val);
+ matcher.matches();
+ String name = matcher.group("name");
+ String value = matcher.group("value");
+ String separator = matcher.group("separator");
+ return apply(name, separator, value != null ? value : "", getEnv(name));
+ }
+ }
+
+ /**
+ * Implement the logic for missing and unset variables
+ *
+ * @param name - variable name in the template
+ * @param separator - separator in the template, can be :-, -, :?, ?
+ * @param value - default value or the error in the template
+ * @param environment - the value from environment for the provided variable
+ * @return the value to apply in the template
+ */
+ public String apply(String name, String separator, String value, String environment) {
+ if (environment != null && !environment.isEmpty()) {
+ return environment;
+ }
+ // variable is either unset or empty
+ if (separator != null) {
+ // there is a default value or error
+ if (separator.equals("?")) {
+ if (environment == null) {
+ throw new MissingEnvironmentVariableException(
+ "Missing mandatory variable " + name + ": " + value);
+ }
+ }
+ if (separator.equals(":?")) {
+ if (environment == null) {
+ throw new MissingEnvironmentVariableException(
+ "Missing mandatory variable " + name + ": " + value);
+ }
+ if (environment.isEmpty()) {
+ throw new MissingEnvironmentVariableException(
+ "Empty mandatory variable " + name + ": " + value);
+ }
+ }
+ if (separator.startsWith(":")) {
+ if (environment == null || environment.isEmpty()) {
+ return value;
+ }
+ } else {
+ if (environment == null) {
+ return value;
+ }
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Get value of the environment variable
+ *
+ * @param key - the name of the variable
+ * @return value or null if not set
+ */
+ public String getEnv(String key) {
+ return System.getenv(key);
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/error/Mark.java b/src/main/java/org/yaml/snakeyaml/error/Mark.java
index 7e1d546e..367ee6bb 100644
--- a/src/main/java/org/yaml/snakeyaml/error/Mark.java
+++ b/src/main/java/org/yaml/snakeyaml/error/Mark.java
@@ -1,132 +1,161 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.error;
+import java.io.Serializable;
import org.yaml.snakeyaml.scanner.Constant;
/**
- * It's just a record and its only use is producing nice error messages. Parser
- * does not use it for any other purposes.
+ * It's just a record and its only use is producing nice error messages. Parser does not use it for
+ * any other purposes.
*/
-public final class Mark {
- private String name;
- private int index;
- private int line;
- private int column;
- private String buffer;
- private int pointer;
-
- public Mark(String name, int index, int line, int column, String buffer, int pointer) {
- super();
- this.name = name;
- this.index = index;
- this.line = line;
- this.column = column;
- this.buffer = buffer;
- this.pointer = pointer;
- }
+public final class Mark implements Serializable {
- private boolean isLineBreak(char ch) {
- return Constant.NULL_OR_LINEBR.has(ch);
- }
+ private final String name;
+ private final int index;
+ private final int line;
+ private final int column;
+ private final int[] buffer;
+ private final int pointer;
- public String get_snippet(int indent, int max_length) {
- if (buffer == null) {
- return null;
- }
- float half = max_length / 2 - 1;
- int start = pointer;
- String head = "";
- while ((start > 0) && !isLineBreak(buffer.charAt(start - 1))) {
- start -= 1;
- if (pointer - start > half) {
- head = " ... ";
- start += 5;
- break;
- }
- }
- String tail = "";
- int end = pointer;
- while ((end < buffer.length()) && !isLineBreak(buffer.charAt(end))) {
- end += 1;
- if (end - pointer > half) {
- tail = " ... ";
- end -= 5;
- break;
- }
- }
- String snippet = buffer.substring(start, end);
- StringBuilder result = new StringBuilder();
- for (int i = 0; i < indent; i++) {
- result.append(" ");
- }
- result.append(head);
- result.append(snippet);
- result.append(tail);
- result.append("\n");
- for (int i = 0; i < indent + pointer - start + head.length(); i++) {
- result.append(" ");
- }
- result.append("^");
- return result.toString();
+ private static int[] toCodePoints(char[] str) {
+ int[] codePoints = new int[Character.codePointCount(str, 0, str.length)];
+ for (int i = 0, c = 0; i < str.length; c++) {
+ int cp = Character.codePointAt(str, i);
+ codePoints[c] = cp;
+ i += Character.charCount(cp);
}
+ return codePoints;
+ }
- public String get_snippet() {
- return get_snippet(4, 75);
- }
+ public Mark(String name, int index, int line, int column, char[] str, int pointer) {
+ this(name, index, line, column, toCodePoints(str), pointer);
+ }
- @Override
- public String toString() {
- String snippet = get_snippet();
- StringBuilder where = new StringBuilder(" in ");
- where.append(name);
- where.append(", line ");
- where.append(line + 1);
- where.append(", column ");
- where.append(column + 1);
- if (snippet != null) {
- where.append(":\n");
- where.append(snippet);
- }
- return where.toString();
- }
+ /*
+ * Existed in older versions but replaced with {@code char[]}-based constructor. Restored in v1.22
+ * for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link Mark#Mark(String, int, int, int, char[], int)}.
+ */
+ @Deprecated
+ public Mark(String name, int index, int line, int column, String buffer, int pointer) {
+ this(name, index, line, column, buffer.toCharArray(), pointer);
+ }
- public String getName() {
- return name;
- }
+ public Mark(String name, int index, int line, int column, int[] buffer, int pointer) {
+ super();
+ this.name = name;
+ this.index = index;
+ this.line = line;
+ this.column = column;
+ this.buffer = buffer;
+ this.pointer = pointer;
+ }
- /**
- * starts with 0
- */
- public int getLine() {
- return line;
- }
+ private boolean isLineBreak(int c) {
+ return Constant.NULL_OR_LINEBR.has(c);
+ }
- /**
- * starts with 0
- */
- public int getColumn() {
- return column;
+ public String get_snippet(int indent, int max_length) {
+ float half = max_length / 2f - 1f;
+ int start = pointer;
+ String head = "";
+ while ((start > 0) && !isLineBreak(buffer[start - 1])) {
+ start -= 1;
+ if (pointer - start > half) {
+ head = " ... ";
+ start += 5;
+ break;
+ }
+ }
+ String tail = "";
+ int end = pointer;
+ while ((end < buffer.length) && !isLineBreak(buffer[end])) {
+ end += 1;
+ if (end - pointer > half) {
+ tail = " ... ";
+ end -= 5;
+ break;
+ }
}
- /**
- * starts with 0
- */
- public int getIndex() {
- return index;
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ result.append(" ");
+ }
+ result.append(head);
+ for (int i = start; i < end; i++) {
+ result.appendCodePoint(buffer[i]);
}
+ result.append(tail);
+ result.append("\n");
+ for (int i = 0; i < indent + pointer - start + head.length(); i++) {
+ result.append(" ");
+ }
+ result.append("^");
+ return result.toString();
+ }
+
+ public String get_snippet() {
+ return get_snippet(4, 75);
+ }
+
+ @Override
+ public String toString() {
+ String snippet = get_snippet();
+ String builder =
+ " in " + name + ", line " + (line + 1) + ", column " + (column + 1) + ":\n" + snippet;
+ return builder;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * starts with 0
+ *
+ * @return line number
+ */
+ public int getLine() {
+ return line;
+ }
+
+ /**
+ * starts with 0
+ *
+ * @return column number
+ */
+ public int getColumn() {
+ return column;
+ }
+
+ /**
+ * starts with 0
+ *
+ * @return character number
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ public int[] getBuffer() {
+ return buffer;
+ }
+ public int getPointer() {
+ return pointer;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/error/MarkedYAMLException.java b/src/main/java/org/yaml/snakeyaml/error/MarkedYAMLException.java
index 4e44ab9b..1e94902e 100644
--- a/src/main/java/org/yaml/snakeyaml/error/MarkedYAMLException.java
+++ b/src/main/java/org/yaml/snakeyaml/error/MarkedYAMLException.java
@@ -1,101 +1,99 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.error;
public class MarkedYAMLException extends YAMLException {
- private static final long serialVersionUID = -9119388488683035101L;
- private String context;
- private Mark contextMark;
- private String problem;
- private Mark problemMark;
- private String note;
+ private static final long serialVersionUID = -9119388488683035101L;
+ private final String context;
+ private final Mark contextMark;
+ private final String problem;
+ private final Mark problemMark;
+ private final String note;
- protected MarkedYAMLException(String context, Mark contextMark, String problem,
- Mark problemMark, String note) {
- this(context, contextMark, problem, problemMark, note, null);
- }
+ protected MarkedYAMLException(String context, Mark contextMark, String problem, Mark problemMark,
+ String note) {
+ this(context, contextMark, problem, problemMark, note, null);
+ }
- protected MarkedYAMLException(String context, Mark contextMark, String problem,
- Mark problemMark, String note, Throwable cause) {
- super(context + "; " + problem + "; " + problemMark, cause);
- this.context = context;
- this.contextMark = contextMark;
- this.problem = problem;
- this.problemMark = problemMark;
- this.note = note;
- }
+ protected MarkedYAMLException(String context, Mark contextMark, String problem, Mark problemMark,
+ String note, Throwable cause) {
+ super(context + "; " + problem + "; " + problemMark, cause);
+ this.context = context;
+ this.contextMark = contextMark;
+ this.problem = problem;
+ this.problemMark = problemMark;
+ this.note = note;
+ }
- protected MarkedYAMLException(String context, Mark contextMark, String problem, Mark problemMark) {
- this(context, contextMark, problem, problemMark, null, null);
- }
+ protected MarkedYAMLException(String context, Mark contextMark, String problem,
+ Mark problemMark) {
+ this(context, contextMark, problem, problemMark, null, null);
+ }
- protected MarkedYAMLException(String context, Mark contextMark, String problem,
- Mark problemMark, Throwable cause) {
- this(context, contextMark, problem, problemMark, null, cause);
- }
+ protected MarkedYAMLException(String context, Mark contextMark, String problem, Mark problemMark,
+ Throwable cause) {
+ this(context, contextMark, problem, problemMark, null, cause);
+ }
- @Override
- public String getMessage() {
- return toString();
- }
+ @Override
+ public String getMessage() {
+ return toString();
+ }
- @Override
- public String toString() {
- StringBuilder lines = new StringBuilder();
- if (context != null) {
- lines.append(context);
- lines.append("\n");
- }
- if (contextMark != null
- && (problem == null || problemMark == null
- || contextMark.getName().equals(problemMark.getName())
- || (contextMark.getLine() != problemMark.getLine()) || (contextMark
- .getColumn() != problemMark.getColumn()))) {
- lines.append(contextMark.toString());
- lines.append("\n");
- }
- if (problem != null) {
- lines.append(problem);
- lines.append("\n");
- }
- if (problemMark != null) {
- lines.append(problemMark.toString());
- lines.append("\n");
- }
- if (note != null) {
- lines.append(note);
- lines.append("\n");
- }
- return lines.toString();
+ @Override
+ public String toString() {
+ StringBuilder lines = new StringBuilder();
+ if (context != null) {
+ lines.append(context);
+ lines.append("\n");
}
-
- public String getContext() {
- return context;
+ if (contextMark != null && (problem == null || problemMark == null
+ || contextMark.getName().equals(problemMark.getName())
+ || (contextMark.getLine() != problemMark.getLine())
+ || (contextMark.getColumn() != problemMark.getColumn()))) {
+ lines.append(contextMark);
+ lines.append("\n");
}
-
- public Mark getContextMark() {
- return contextMark;
+ if (problem != null) {
+ lines.append(problem);
+ lines.append("\n");
}
-
- public String getProblem() {
- return problem;
+ if (problemMark != null) {
+ lines.append(problemMark);
+ lines.append("\n");
}
-
- public Mark getProblemMark() {
- return problemMark;
+ if (note != null) {
+ lines.append(note);
+ lines.append("\n");
}
+ return lines.toString();
+ }
+
+ public String getContext() {
+ return context;
+ }
+
+ public Mark getContextMark() {
+ return contextMark;
+ }
+
+ public String getProblem() {
+ return problem;
+ }
+
+ public Mark getProblemMark() {
+ return problemMark;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/error/MissingEnvironmentVariableException.java b/src/main/java/org/yaml/snakeyaml/error/MissingEnvironmentVariableException.java
new file mode 100644
index 00000000..382c949d
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/error/MissingEnvironmentVariableException.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.error;
+
+/**
+ * Indicate missing mandatory environment variable in the template Used by EnvScalarConstructor
+ */
+public class MissingEnvironmentVariableException extends YAMLException {
+
+ public MissingEnvironmentVariableException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/error/YAMLException.java b/src/main/java/org/yaml/snakeyaml/error/YAMLException.java
index af7189b3..f712f229 100644
--- a/src/main/java/org/yaml/snakeyaml/error/YAMLException.java
+++ b/src/main/java/org/yaml/snakeyaml/error/YAMLException.java
@@ -1,32 +1,31 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.error;
public class YAMLException extends RuntimeException {
- private static final long serialVersionUID = -4738336175050337570L;
- public YAMLException(String message) {
- super(message);
- }
+ private static final long serialVersionUID = -4738336175050337570L;
- public YAMLException(Throwable cause) {
- super(cause);
- }
+ public YAMLException(String message) {
+ super(message);
+ }
- public YAMLException(String message, Throwable cause) {
- super(message, cause);
- }
+ public YAMLException(Throwable cause) {
+ super(cause);
+ }
+
+ public YAMLException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/AliasEvent.java b/src/main/java/org/yaml/snakeyaml/events/AliasEvent.java
index e0dcf7f8..75f2fb4c 100644
--- a/src/main/java/org/yaml/snakeyaml/events/AliasEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/AliasEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -21,12 +19,16 @@ import org.yaml.snakeyaml.error.Mark;
* Marks the inclusion of a previously anchored node.
*/
public final class AliasEvent extends NodeEvent {
- public AliasEvent(String anchor, Mark startMark, Mark endMark) {
- super(anchor, startMark, endMark);
- }
- @Override
- public boolean is(Event.ID id) {
- return ID.Alias == id;
+ public AliasEvent(String anchor, Mark startMark, Mark endMark) {
+ super(anchor, startMark, endMark);
+ if (anchor == null) {
+ throw new NullPointerException("anchor is not specified for alias");
}
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.Alias;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/CollectionEndEvent.java b/src/main/java/org/yaml/snakeyaml/events/CollectionEndEvent.java
index b36f32c3..d9135b5e 100644
--- a/src/main/java/org/yaml/snakeyaml/events/CollectionEndEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/CollectionEndEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -22,7 +20,7 @@ import org.yaml.snakeyaml.error.Mark;
*/
public abstract class CollectionEndEvent extends Event {
- public CollectionEndEvent(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public CollectionEndEvent(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/CollectionStartEvent.java b/src/main/java/org/yaml/snakeyaml/events/CollectionStartEvent.java
index 9a772996..e03e514a 100644
--- a/src/main/java/org/yaml/snakeyaml/events/CollectionStartEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/CollectionStartEvent.java
@@ -1,73 +1,91 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
* Base class for the start events of the collection nodes.
*/
public abstract class CollectionStartEvent extends NodeEvent {
- private final String tag;
- // The implicit flag of a collection start event indicates if the tag may be
- // omitted when the collection is emitted
- private final boolean implicit;
- // flag indicates if a collection is block or flow
- private final Boolean flowStyle;
- public CollectionStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
- Mark endMark, Boolean flowStyle) {
- super(anchor, startMark, endMark);
- this.tag = tag;
- this.implicit = implicit;
- this.flowStyle = flowStyle;
- }
+ private final String tag;
+ // The implicit flag of a collection start event indicates if the tag may be
+ // omitted when the collection is emitted
+ private final boolean implicit;
+ // flag indicates if a collection is block or flow
+ private final DumperOptions.FlowStyle flowStyle;
- /**
- * Tag of this collection.
- *
- * @return The tag of this collection, or <code>null</code> if no explicit
- * tag is available.
- */
- public String getTag() {
- return this.tag;
+ public CollectionStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, DumperOptions.FlowStyle flowStyle) {
+ super(anchor, startMark, endMark);
+ this.tag = tag;
+ this.implicit = implicit;
+ if (flowStyle == null) {
+ throw new NullPointerException("Flow style must be provided.");
}
+ this.flowStyle = flowStyle;
+ }
- /**
- * <code>true</code> if the tag can be omitted while this collection is
- * emitted.
- *
- * @return True if the tag can be omitted while this collection is emitted.
- */
- public boolean getImplicit() {
- return this.implicit;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based constructor.
+ * Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link
+ * CollectionStartEvent#CollectionStartEvent(String, String, boolean, Mark, Mark,
+ * org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public CollectionStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, Boolean flowStyle) {
+ this(anchor, tag, implicit, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
- /**
- * <code>true</code> if this collection is in flow style, <code>false</code>
- * for block style.
- *
- * @return If this collection is in flow style.
- */
- public Boolean getFlowStyle() {
- return this.flowStyle;
- }
+ /**
+ * Tag of this collection.
+ *
+ * @return The tag of this collection, or <code>null</code> if no explicit tag is available.
+ */
+ public String getTag() {
+ return this.tag;
+ }
- @Override
- protected String getArguments() {
- return super.getArguments() + ", tag=" + tag + ", implicit=" + implicit;
- }
+ /**
+ * <code>true</code> if the tag can be omitted while this collection is emitted.
+ *
+ * @return True if the tag can be omitted while this collection is emitted.
+ */
+ public boolean getImplicit() {
+ return this.implicit;
+ }
+
+ /**
+ * <code>true</code> if this collection is in flow style, <code>false</code> for block style.
+ *
+ * @return If this collection is in flow style.
+ */
+ public DumperOptions.FlowStyle getFlowStyle() {
+ return this.flowStyle;
+ }
+
+ @Override
+ protected String getArguments() {
+ return super.getArguments() + ", tag=" + tag + ", implicit=" + implicit;
+ }
+
+ public boolean isFlow() {
+ return DumperOptions.FlowStyle.FLOW == flowStyle;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java b/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java
new file mode 100644
index 00000000..3b16e387
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.events;
+
+import org.yaml.snakeyaml.comments.CommentType;
+import org.yaml.snakeyaml.error.Mark;
+
+/**
+ * Marks a comment block value.
+ */
+public final class CommentEvent extends Event {
+
+ private final CommentType type;
+ private final String value;
+
+ public CommentEvent(CommentType type, String value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ if (type == null) {
+ throw new NullPointerException("Event Type must be provided.");
+ }
+ this.type = type;
+ if (value == null) {
+ throw new NullPointerException("Value must be provided.");
+ }
+ this.value = value;
+ }
+
+ /**
+ * String representation of the value.
+ * <p>
+ * Without quotes and escaping.
+ * </p>
+ *
+ * @return Value a comment line string without the leading '#' or a blank line.
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * The comment type.
+ *
+ * @return the commentType.
+ */
+ public CommentType getCommentType() {
+ return this.type;
+ }
+
+ @Override
+ protected String getArguments() {
+ return super.getArguments() + "type=" + type + ", value=" + value;
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.Comment;
+ }
+
+}
diff --git a/src/main/java/org/yaml/snakeyaml/events/DocumentEndEvent.java b/src/main/java/org/yaml/snakeyaml/events/DocumentEndEvent.java
index 30fe439f..17cdf716 100644
--- a/src/main/java/org/yaml/snakeyaml/events/DocumentEndEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/DocumentEndEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -24,19 +22,20 @@ import org.yaml.snakeyaml.error.Mark;
* </p>
*/
public final class DocumentEndEvent extends Event {
- private final boolean explicit;
- public DocumentEndEvent(Mark startMark, Mark endMark, boolean explicit) {
- super(startMark, endMark);
- this.explicit = explicit;
- }
+ private final boolean explicit;
- public boolean getExplicit() {
- return explicit;
- }
+ public DocumentEndEvent(Mark startMark, Mark endMark, boolean explicit) {
+ super(startMark, endMark);
+ this.explicit = explicit;
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.DocumentEnd == id;
- }
+ public boolean getExplicit() {
+ return explicit;
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.DocumentEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/DocumentStartEvent.java b/src/main/java/org/yaml/snakeyaml/events/DocumentStartEvent.java
index fa24cdfb..0eab4322 100644
--- a/src/main/java/org/yaml/snakeyaml/events/DocumentStartEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/DocumentStartEvent.java
@@ -1,22 +1,19 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
import java.util.Map;
-
import org.yaml.snakeyaml.DumperOptions.Version;
import org.yaml.snakeyaml.error.Mark;
@@ -27,50 +24,45 @@ import org.yaml.snakeyaml.error.Mark;
* </p>
*/
public final class DocumentStartEvent extends Event {
- private final boolean explicit;
- private final Version version;
- private final Map<String, String> tags;
- public DocumentStartEvent(Mark startMark, Mark endMark, boolean explicit, Version version,
- Map<String, String> tags) {
- super(startMark, endMark);
- this.explicit = explicit;
- this.version = version;
- // TODO enforce not null
- // if (tags == null) {
- // throw new NullPointerException("Tags must be provided.");
- // }
- this.tags = tags;
- }
+ private final boolean explicit;
+ private final Version version;
+ private final Map<String, String> tags;
+
+ public DocumentStartEvent(Mark startMark, Mark endMark, boolean explicit, Version version,
+ Map<String, String> tags) {
+ super(startMark, endMark);
+ this.explicit = explicit;
+ this.version = version;
+ this.tags = tags;
+ }
- public boolean getExplicit() {
- return explicit;
- }
+ public boolean getExplicit() {
+ return explicit;
+ }
- /**
- * YAML version the document conforms to.
- *
- * @return <code>null</code>if the document has no explicit
- * <code>%YAML</code> directive. Otherwise an array with two
- * components, the major and minor part of the version (in this
- * order).
- */
- public Version getVersion() {
- return version;
- }
+ /**
+ * YAML version the document conforms to.
+ *
+ * @return <code>null</code>if the document has no explicit <code>%YAML</code> directive.
+ * Otherwise an array with two components, the major and minor part of the version (in
+ * this order).
+ */
+ public Version getVersion() {
+ return version;
+ }
- /**
- * Tag shorthands as defined by the <code>%TAG</code> directive.
- *
- * @return Mapping of 'handles' to 'prefixes' (the handles include the '!'
- * characters).
- */
- public Map<String, String> getTags() {
- return tags;
- }
+ /**
+ * Tag shorthands as defined by the <code>%TAG</code> directive.
+ *
+ * @return Mapping of 'handles' to 'prefixes' (the handles include the '!' characters).
+ */
+ public Map<String, String> getTags() {
+ return tags;
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.DocumentStart == id;
- }
+ @Override
+ public Event.ID getEventId() {
+ return ID.DocumentStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/Event.java b/src/main/java/org/yaml/snakeyaml/events/Event.java
index 4ebfa921..62c85b6e 100644
--- a/src/main/java/org/yaml/snakeyaml/events/Event.java
+++ b/src/main/java/org/yaml/snakeyaml/events/Event.java
@@ -1,77 +1,94 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
import org.yaml.snakeyaml.error.Mark;
/**
- * Basic unit of output from a {@link org.yaml.snakeyaml.parser.Parser} or input
- * of a {@link org.yaml.snakeyaml.emitter.Emitter}.
+ * Basic unit of output from a {@link org.yaml.snakeyaml.parser.Parser} or input of a
+ * {@link org.yaml.snakeyaml.emitter.Emitter}.
*/
public abstract class Event {
- public enum ID {
- Alias, DocumentEnd, DocumentStart, MappingEnd, MappingStart, Scalar, SequenceEnd, SequenceStart, StreamEnd, StreamStart
- }
- private final Mark startMark;
- private final Mark endMark;
+ public enum ID {
+ Alias, Comment, DocumentEnd, DocumentStart, MappingEnd, MappingStart, Scalar, SequenceEnd, SequenceStart, StreamEnd, StreamStart
+ }
- public Event(Mark startMark, Mark endMark) {
- this.startMark = startMark;
- this.endMark = endMark;
- }
+ private final Mark startMark;
+ private final Mark endMark;
- public String toString() {
- return "<" + this.getClass().getName() + "(" + getArguments() + ")>";
- }
+ public Event(Mark startMark, Mark endMark) {
+ this.startMark = startMark;
+ this.endMark = endMark;
+ }
- public Mark getStartMark() {
- return startMark;
- }
+ public String toString() {
+ return "<" + this.getClass().getName() + "(" + getArguments() + ")>";
+ }
- public Mark getEndMark() {
- return endMark;
- }
+ public Mark getStartMark() {
+ return startMark;
+ }
- /**
- * @see "__repr__ for Event in PyYAML"
- */
- protected String getArguments() {
- return "";
- }
+ public Mark getEndMark() {
+ return endMark;
+ }
- public abstract boolean is(Event.ID id);
+ /**
+ * Generate human readable representation of the Event
+ *
+ * @see "__repr__ for Event in PyYAML"
+ * @return representation fore humans
+ */
+ protected String getArguments() {
+ return "";
+ }
- /*
- * for tests only
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Event) {
- return toString().equals(obj.toString());
- } else {
- return false;
- }
- }
+ /**
+ * Check if the Event is of the provided kind
+ *
+ * @param id - the Event.ID enum
+ * @return true then this Event of the provided type
+ */
+ public boolean is(Event.ID id) {
+ return getEventId() == id;
+ }
- /*
- * for tests only
- */
- @Override
- public int hashCode() {
- return toString().hashCode();
+ /**
+ * Get the type (kind) if this Event
+ *
+ * @return the ID of this Event
+ */
+ public abstract Event.ID getEventId();
+
+ /*
+ * for tests only
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Event) {
+ return toString().equals(obj.toString());
+ } else {
+ return false;
}
+ }
+
+ /*
+ * for tests only
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/ImplicitTuple.java b/src/main/java/org/yaml/snakeyaml/events/ImplicitTuple.java
index 86a99acc..55b7dfc9 100644
--- a/src/main/java/org/yaml/snakeyaml/events/ImplicitTuple.java
+++ b/src/main/java/org/yaml/snakeyaml/events/ImplicitTuple.java
@@ -1,58 +1,54 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
/**
- * The implicit flag of a scalar event is a pair of boolean values that indicate
- * if the tag may be omitted when the scalar is emitted in a plain and non-plain
- * style correspondingly.
- *
+ * The implicit flag of a scalar event is a pair of boolean values that indicate if the tag may be
+ * omitted when the scalar is emitted in a plain and non-plain style correspondingly.
+ *
* @see <a href="http://pyyaml.org/wiki/PyYAMLDocumentation#Events">Events</a>
*/
public class ImplicitTuple {
- private final boolean plain;
- private final boolean nonPlain;
- public ImplicitTuple(boolean plain, boolean nonplain) {
- this.plain = plain;
- this.nonPlain = nonplain;
- }
+ private final boolean plain;
+ private final boolean nonPlain;
+
+ public ImplicitTuple(boolean plain, boolean nonplain) {
+ this.plain = plain;
+ this.nonPlain = nonplain;
+ }
- /**
- * @return true when tag may be omitted when the scalar is emitted in a
- * plain style.
- */
- public boolean canOmitTagInPlainScalar() {
- return plain;
- }
+ /**
+ * @return true when tag may be omitted when the scalar is emitted in a plain style.
+ */
+ public boolean canOmitTagInPlainScalar() {
+ return plain;
+ }
- /**
- * @return true when tag may be omitted when the scalar is emitted in a
- * non-plain style.
- */
- public boolean canOmitTagInNonPlainScalar() {
- return nonPlain;
- }
+ /**
+ * @return true when tag may be omitted when the scalar is emitted in a non-plain style.
+ */
+ public boolean canOmitTagInNonPlainScalar() {
+ return nonPlain;
+ }
- public boolean bothFalse() {
- return !plain && !nonPlain;
- }
+ public boolean bothFalse() {
+ return !plain && !nonPlain;
+ }
- @Override
- public String toString() {
- return "implicit=[" + plain + ", " + nonPlain + "]";
- }
+ @Override
+ public String toString() {
+ return "implicit=[" + plain + ", " + nonPlain + "]";
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/MappingEndEvent.java b/src/main/java/org/yaml/snakeyaml/events/MappingEndEvent.java
index 618c9163..bfa684fb 100644
--- a/src/main/java/org/yaml/snakeyaml/events/MappingEndEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/MappingEndEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -19,17 +17,17 @@ import org.yaml.snakeyaml.error.Mark;
/**
* Marks the end of a mapping node.
- *
+ *
* @see MappingStartEvent
*/
public final class MappingEndEvent extends CollectionEndEvent {
- public MappingEndEvent(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public MappingEndEvent(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.MappingEnd == id;
- }
+ @Override
+ public Event.ID getEventId() {
+ return ID.MappingEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/MappingStartEvent.java b/src/main/java/org/yaml/snakeyaml/events/MappingStartEvent.java
index 412e4d54..fab822ef 100644
--- a/src/main/java/org/yaml/snakeyaml/events/MappingStartEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/MappingStartEvent.java
@@ -1,44 +1,57 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
* Marks the beginning of a mapping node.
* <p>
* This event is followed by a number of key value pairs. <br>
- * The pairs are not in any particular order. However, the value always directly
- * follows the corresponding key. <br>
+ * The pairs are not in any particular order. However, the value always directly follows the
+ * corresponding key. <br>
* After the key value pairs follows a {@link MappingEndEvent}.
* </p>
* <p>
* There must be an even number of node events between the start and end event.
* </p>
- *
+ *
* @see MappingEndEvent
*/
public final class MappingStartEvent extends CollectionStartEvent {
- public MappingStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
- Mark endMark, Boolean flowStyle) {
- super(anchor, tag, implicit, startMark, endMark, flowStyle);
- }
- @Override
- public boolean is(Event.ID id) {
- return ID.MappingStart == id;
- }
+ public MappingStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, DumperOptions.FlowStyle flowStyle) {
+ super(anchor, tag, implicit, startMark, endMark, flowStyle);
+ }
+
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based constructor.
+ * Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link MappingStartEvent#CollectionStartEvent(String,
+ * String, boolean, Mark, Mark, org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public MappingStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, Boolean flowStyle) {
+ this(anchor, tag, implicit, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.MappingStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/NodeEvent.java b/src/main/java/org/yaml/snakeyaml/events/NodeEvent.java
index f0af48b9..4db308e6 100644
--- a/src/main/java/org/yaml/snakeyaml/events/NodeEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/NodeEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -22,28 +20,27 @@ import org.yaml.snakeyaml.error.Mark;
*/
public abstract class NodeEvent extends Event {
- private final String anchor;
+ private final String anchor;
- public NodeEvent(String anchor, Mark startMark, Mark endMark) {
- super(startMark, endMark);
- this.anchor = anchor;
- }
+ public NodeEvent(String anchor, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ this.anchor = anchor;
+ }
- /**
- * Node anchor by which this node might later be referenced by a
- * {@link AliasEvent}.
- * <p>
- * Note that {@link AliasEvent}s are by it self <code>NodeEvent</code>s and
- * use this property to indicate the referenced anchor.
- *
- * @return Anchor of this node or <code>null</code> if no anchor is defined.
- */
- public String getAnchor() {
- return this.anchor;
- }
+ /**
+ * Node anchor by which this node might later be referenced by a {@link AliasEvent}.
+ * <p>
+ * Note that {@link AliasEvent}s are by it self <code>NodeEvent</code>s and use this property to
+ * indicate the referenced anchor.
+ *
+ * @return Anchor of this node or <code>null</code> if no anchor is defined.
+ */
+ public String getAnchor() {
+ return this.anchor;
+ }
- @Override
- protected String getArguments() {
- return "anchor=" + anchor;
- }
+ @Override
+ protected String getArguments() {
+ return "anchor=" + anchor;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/ScalarEvent.java b/src/main/java/org/yaml/snakeyaml/events/ScalarEvent.java
index 7f07a62b..ed2ea0cc 100644
--- a/src/main/java/org/yaml/snakeyaml/events/ScalarEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/ScalarEvent.java
@@ -1,101 +1,132 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
* Marks a scalar value.
*/
public final class ScalarEvent extends NodeEvent {
- private final String tag;
- // style flag of a scalar event indicates the style of the scalar. Possible
- // values are None, '', '\'', '"', '|', '>'
- private final Character style;
- private final String value;
- // The implicit flag of a scalar event is a pair of boolean values that
- // indicate if the tag may be omitted when the scalar is emitted in a plain
- // and non-plain style correspondingly.
- private final ImplicitTuple implicit;
- public ScalarEvent(String anchor, String tag, ImplicitTuple implicit, String value,
- Mark startMark, Mark endMark, Character style) {
- super(anchor, startMark, endMark);
- this.tag = tag;
- this.implicit = implicit;
- this.value = value;
- this.style = style;
- }
+ private final String tag;
+ // style flag of a scalar event indicates the style of the scalar. Possible
+ // values are None, '', '\'', '"', '|', '>'
+ private final DumperOptions.ScalarStyle style;
+ private final String value;
+ // The implicit flag of a scalar event is a pair of boolean values that
+ // indicate if the tag may be omitted when the scalar is emitted in a plain
+ // and non-plain style correspondingly.
+ private final ImplicitTuple implicit;
- /**
- * Tag of this scalar.
- *
- * @return The tag of this scalar, or <code>null</code> if no explicit tag
- * is available.
- */
- public String getTag() {
- return this.tag;
+ public ScalarEvent(String anchor, String tag, ImplicitTuple implicit, String value,
+ Mark startMark, Mark endMark, DumperOptions.ScalarStyle style) {
+ super(anchor, startMark, endMark);
+ this.tag = tag;
+ this.implicit = implicit;
+ if (value == null) {
+ throw new NullPointerException("Value must be provided.");
}
-
- /**
- * Style of the scalar.
- * <dl>
- * <dt>null</dt>
- * <dd>Flow Style - Plain</dd>
- * <dt>'\''</dt>
- * <dd>Flow Style - Single-Quoted</dd>
- * <dt>'"'</dt>
- * <dd>Flow Style - Double-Quoted</dd>
- * <dt>'|'</dt>
- * <dd>Block Style - Literal</dd>
- * <dt>'>'</dt>
- * <dd>Block Style - Folded</dd>
- * </dl>
- *
- * @see <a href="http://yaml.org/spec/1.1/#id864487">Kind/Style
- * Combinations</a>
- * @return Style of the scalar.
- */
- public Character getStyle() {
- return this.style;
+ this.value = value;
+ if (style == null) {
+ throw new NullPointerException("Style must be provided.");
}
+ this.style = style;
+ }
- /**
- * String representation of the value.
- * <p>
- * Without quotes and escaping.
- * </p>
- *
- * @return Value as Unicode string.
- */
- public String getValue() {
- return this.value;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.ScalarStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link ScalarEvent#ScalarEvent(String, String,
+ * ImplicitTuple, String, Mark, Mark, org.yaml.snakeyaml.DumperOptions.ScalarStyle) }.
+ */
+ @Deprecated
+ public ScalarEvent(String anchor, String tag, ImplicitTuple implicit, String value,
+ Mark startMark, Mark endMark, Character style) {
+ this(anchor, tag, implicit, value, startMark, endMark,
+ DumperOptions.ScalarStyle.createStyle(style));
+ }
- public ImplicitTuple getImplicit() {
- return this.implicit;
- }
+ /**
+ * Tag of this scalar.
+ *
+ * @return The tag of this scalar, or <code>null</code> if no explicit tag is available.
+ */
+ public String getTag() {
+ return this.tag;
+ }
- @Override
- protected String getArguments() {
- return super.getArguments() + ", tag=" + tag + ", " + implicit + ", value=" + value;
- }
+ /**
+ * Style of the scalar.
+ * <dl>
+ * <dt>null</dt>
+ * <dd>Flow Style - Plain</dd>
+ * <dt>'\''</dt>
+ * <dd>Flow Style - Single-Quoted</dd>
+ * <dt>'"'</dt>
+ * <dd>Flow Style - Double-Quoted</dd>
+ * <dt>'|'</dt>
+ * <dd>Block Style - Literal</dd>
+ * <dt>'&gt;'</dt>
+ * <dd>Block Style - Folded</dd>
+ * </dl>
+ *
+ * @see <a href="http://yaml.org/spec/1.1/#id864487">Kind/Style Combinations</a>
+ * @return Style of the scalar.
+ */
+ public DumperOptions.ScalarStyle getScalarStyle() {
+ return this.style;
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.Scalar == id;
- }
+ /**
+ * @deprecated use getScalarStyle() instead
+ * @return char which is a value behind ScalarStyle
+ */
+ @Deprecated
+ public Character getStyle() {
+ return this.style.getChar();
+ }
+
+ /**
+ * String representation of the value.
+ * <p>
+ * Without quotes and escaping.
+ * </p>
+ *
+ * @return Value as Unicode string.
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ public ImplicitTuple getImplicit() {
+ return this.implicit;
+ }
+
+ @Override
+ protected String getArguments() {
+ return super.getArguments() + ", tag=" + tag + ", " + implicit + ", value=" + value;
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.Scalar;
+ }
+
+ public boolean isPlain() {
+ return style == DumperOptions.ScalarStyle.PLAIN;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/SequenceEndEvent.java b/src/main/java/org/yaml/snakeyaml/events/SequenceEndEvent.java
index a6a61275..dc1b610b 100644
--- a/src/main/java/org/yaml/snakeyaml/events/SequenceEndEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/SequenceEndEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -19,17 +17,17 @@ import org.yaml.snakeyaml.error.Mark;
/**
* Marks the end of a sequence.
- *
+ *
* @see SequenceStartEvent
*/
public final class SequenceEndEvent extends CollectionEndEvent {
- public SequenceEndEvent(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public SequenceEndEvent(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.SequenceEnd == id;
- }
+ @Override
+ public Event.ID getEventId() {
+ return ID.SequenceEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/SequenceStartEvent.java b/src/main/java/org/yaml/snakeyaml/events/SequenceStartEvent.java
index eb7b910f..944f2e46 100644
--- a/src/main/java/org/yaml/snakeyaml/events/SequenceStartEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/SequenceStartEvent.java
@@ -1,39 +1,51 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
* Marks the beginning of a sequence node.
* <p>
- * This event is followed by the elements contained in the sequence, and a
- * {@link SequenceEndEvent}.
+ * This event is followed by the elements contained in the sequence, and a {@link SequenceEndEvent}.
* </p>
- *
+ *
* @see SequenceEndEvent
*/
public final class SequenceStartEvent extends CollectionStartEvent {
- public SequenceStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
- Mark endMark, Boolean flowStyle) {
- super(anchor, tag, implicit, startMark, endMark, flowStyle);
- }
- @Override
- public boolean is(Event.ID id) {
- return ID.SequenceStart == id;
- }
+ public SequenceStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, DumperOptions.FlowStyle flowStyle) {
+ super(anchor, tag, implicit, startMark, endMark, flowStyle);
+ }
+
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.SequenceStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link SequenceStartEvent#SequenceStartEvent(String,
+ * String, boolean, Mark, Mark, org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public SequenceStartEvent(String anchor, String tag, boolean implicit, Mark startMark,
+ Mark endMark, Boolean flowStyle) {
+ this(anchor, tag, implicit, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.SequenceStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/StreamEndEvent.java b/src/main/java/org/yaml/snakeyaml/events/StreamEndEvent.java
index 1389c6b6..6dbb02a4 100644
--- a/src/main/java/org/yaml/snakeyaml/events/StreamEndEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/StreamEndEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -20,21 +18,21 @@ import org.yaml.snakeyaml.error.Mark;
/**
* Marks the end of a stream that might have contained multiple documents.
* <p>
- * This event is the last event that a parser emits. Together with
- * {@link StreamStartEvent} (which is the first event a parser emits) they mark
- * the beginning and the end of a stream of documents.
+ * This event is the last event that a parser emits. Together with {@link StreamStartEvent} (which
+ * is the first event a parser emits) they mark the beginning and the end of a stream of documents.
* </p>
* <p>
* See {@link Event} for an exemplary output.
* </p>
*/
public final class StreamEndEvent extends Event {
- public StreamEndEvent(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
- @Override
- public boolean is(Event.ID id) {
- return ID.StreamEnd == id;
- }
+ public StreamEndEvent(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
+
+ @Override
+ public Event.ID getEventId() {
+ return ID.StreamEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/events/StreamStartEvent.java b/src/main/java/org/yaml/snakeyaml/events/StreamStartEvent.java
index 42e6c763..08dededf 100644
--- a/src/main/java/org/yaml/snakeyaml/events/StreamStartEvent.java
+++ b/src/main/java/org/yaml/snakeyaml/events/StreamStartEvent.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.events;
@@ -20,9 +18,8 @@ import org.yaml.snakeyaml.error.Mark;
/**
* Marks the start of a stream that might contain multiple documents.
* <p>
- * This event is the first event that a parser emits. Together with
- * {@link StreamEndEvent} (which is the last event a parser emits) they mark the
- * beginning and the end of a stream of documents.
+ * This event is the first event that a parser emits. Together with {@link StreamEndEvent} (which is
+ * the last event a parser emits) they mark the beginning and the end of a stream of documents.
* </p>
* <p>
* See {@link Event} for an exemplary output.
@@ -30,12 +27,12 @@ import org.yaml.snakeyaml.error.Mark;
*/
public final class StreamStartEvent extends Event {
- public StreamStartEvent(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public StreamStartEvent(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public boolean is(Event.ID id) {
- return ID.StreamStart == id;
- }
+ @Override
+ public Event.ID getEventId() {
+ return ID.StreamStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactConstructor.java b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactConstructor.java
index ec8f1b69..1a722bd1 100644
--- a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactConstructor.java
+++ b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactConstructor.java
@@ -1,21 +1,18 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.extensions.compactnotation;
-import java.beans.IntrospectionException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -23,7 +20,6 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
import org.yaml.snakeyaml.constructor.Construct;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.error.YAMLException;
@@ -38,199 +34,200 @@ import org.yaml.snakeyaml.nodes.SequenceNode;
* Construct a custom Java instance out of a compact object notation format.
*/
public class CompactConstructor extends Constructor {
- private static final Pattern GUESS_COMPACT = Pattern
- .compile("\\p{Alpha}.*\\s*\\((?:,?\\s*(?:(?:\\w*)|(?:\\p{Alpha}\\w*\\s*=.+))\\s*)+\\)");
- private static final Pattern FIRST_PATTERN = Pattern.compile("(\\p{Alpha}.*)(\\s*)\\((.*?)\\)");
- private static final Pattern PROPERTY_NAME_PATTERN = Pattern
- .compile("\\s*(\\p{Alpha}\\w*)\\s*=(.+)");
- private Construct compactConstruct;
-
- protected Object constructCompactFormat(ScalarNode node, CompactData data) {
- try {
- Object obj = createInstance(node, data);
- Map<String, Object> properties = new HashMap<String, Object>(data.getProperties());
- setProperties(obj, properties);
- return obj;
- } catch (Exception e) {
- throw new YAMLException(e);
- }
+
+ private static final Pattern GUESS_COMPACT = Pattern
+ .compile("\\p{Alpha}.*\\s*\\((?:,?\\s*(?:(?:\\w*)|(?:\\p{Alpha}\\w*\\s*=.+))\\s*)+\\)");
+ private static final Pattern FIRST_PATTERN = Pattern.compile("(\\p{Alpha}.*)(\\s*)\\((.*?)\\)");
+ private static final Pattern PROPERTY_NAME_PATTERN =
+ Pattern.compile("\\s*(\\p{Alpha}\\w*)\\s*=(.+)");
+ private Construct compactConstruct;
+
+ protected Object constructCompactFormat(ScalarNode node, CompactData data) {
+ try {
+ Object obj = createInstance(node, data);
+ Map<String, Object> properties = new HashMap<String, Object>(data.getProperties());
+ setProperties(obj, properties);
+ return obj;
+ } catch (Exception e) {
+ throw new YAMLException(e);
+ }
+ }
+
+ protected Object createInstance(ScalarNode node, CompactData data) throws Exception {
+ Class<?> clazz = getClassForName(data.getPrefix());
+ Class<?>[] args = new Class[data.getArguments().size()];
+ for (int i = 0; i < args.length; i++) {
+ // assume all the arguments are Strings
+ args[i] = String.class;
}
+ java.lang.reflect.Constructor<?> c = clazz.getDeclaredConstructor(args);
+ c.setAccessible(true);
+ return c.newInstance(data.getArguments().toArray());
- protected Object createInstance(ScalarNode node, CompactData data) throws Exception {
- Class<?> clazz = getClassForName(data.getPrefix());
- Class<?>[] args = new Class[data.getArguments().size()];
- for (int i = 0; i < args.length; i++) {
- // assume all the arguments are Strings
- args[i] = String.class;
- }
- java.lang.reflect.Constructor<?> c = clazz.getDeclaredConstructor(args);
- c.setAccessible(true);
- return c.newInstance(data.getArguments().toArray());
+ }
+ protected void setProperties(Object bean, Map<String, Object> data) throws Exception {
+ if (data == null) {
+ throw new NullPointerException("Data for Compact Object Notation cannot be null.");
}
-
- protected void setProperties(Object bean, Map<String, Object> data) throws Exception {
- if (data == null) {
- throw new NullPointerException("Data for Compact Object Notation cannot be null.");
- }
- for (Map.Entry<String, Object> entry : data.entrySet()) {
- String key = entry.getKey();
- Property property = getPropertyUtils().getProperty(bean.getClass(), key);
- try {
- property.set(bean, entry.getValue());
- } catch (IllegalArgumentException e) {
- throw new YAMLException("Cannot set property='" + key + "' with value='"
- + data.get(key) + "' (" + data.get(key).getClass() + ") in " + bean);
- }
- }
+ for (Map.Entry<String, Object> entry : data.entrySet()) {
+ String key = entry.getKey();
+ Property property = getPropertyUtils().getProperty(bean.getClass(), key);
+ try {
+ property.set(bean, entry.getValue());
+ } catch (IllegalArgumentException e) {
+ throw new YAMLException("Cannot set property='" + key + "' with value='" + data.get(key)
+ + "' (" + data.get(key).getClass() + ") in " + bean);
+ }
}
+ }
- public CompactData getCompactData(String scalar) {
- if (!scalar.endsWith(")")) {
- return null;
- }
- if (scalar.indexOf('(') < 0) {
+ public CompactData getCompactData(String scalar) {
+ if (!scalar.endsWith(")")) {
+ return null;
+ }
+ if (scalar.indexOf('(') < 0) {
+ return null;
+ }
+ Matcher m = FIRST_PATTERN.matcher(scalar);
+ if (m.matches()) {
+ String tag = m.group(1).trim();
+ String content = m.group(3);
+ CompactData data = new CompactData(tag);
+ if (content.length() == 0) {
+ return data;
+ }
+ String[] names = content.split("\\s*,\\s*");
+ for (int i = 0; i < names.length; i++) {
+ String section = names[i];
+ if (section.indexOf('=') < 0) {
+ data.getArguments().add(section);
+ } else {
+ Matcher sm = PROPERTY_NAME_PATTERN.matcher(section);
+ if (sm.matches()) {
+ String name = sm.group(1);
+ String value = sm.group(2).trim();
+ data.getProperties().put(name, value);
+ } else {
return null;
+ }
}
- Matcher m = FIRST_PATTERN.matcher(scalar);
- if (m.matches()) {
- String tag = m.group(1).trim();
- String content = m.group(3);
- CompactData data = new CompactData(tag);
- if (content.length() == 0)
- return data;
- String[] names = content.split("\\s*,\\s*");
- for (int i = 0; i < names.length; i++) {
- String section = names[i];
- if (section.indexOf('=') < 0) {
- data.getArguments().add(section);
- } else {
- Matcher sm = PROPERTY_NAME_PATTERN.matcher(section);
- if (sm.matches()) {
- String name = sm.group(1);
- String value = sm.group(2).trim();
- data.getProperties().put(name, value);
- } else {
- return null;
- }
- }
- }
- return data;
- }
- return null;
+ }
+ return data;
}
+ return null;
+ }
- private Construct getCompactConstruct() {
- if (compactConstruct == null) {
- compactConstruct = createCompactConstruct();
+ private Construct getCompactConstruct() {
+ if (compactConstruct == null) {
+ compactConstruct = createCompactConstruct();
+ }
+ return compactConstruct;
+ }
+
+ protected Construct createCompactConstruct() {
+ return new ConstructCompactObject();
+ }
+
+ @Override
+ protected Construct getConstructor(Node node) {
+ if (node instanceof MappingNode) {
+ MappingNode mnode = (MappingNode) node;
+ List<NodeTuple> list = mnode.getValue();
+ if (list.size() == 1) {
+ NodeTuple tuple = list.get(0);
+ Node key = tuple.getKeyNode();
+ if (key instanceof ScalarNode) {
+ ScalarNode scalar = (ScalarNode) key;
+ if (GUESS_COMPACT.matcher(scalar.getValue()).matches()) {
+ return getCompactConstruct();
+ }
}
- return compactConstruct;
+ }
+ } else if (node instanceof ScalarNode) {
+ ScalarNode scalar = (ScalarNode) node;
+ if (GUESS_COMPACT.matcher(scalar.getValue()).matches()) {
+ return getCompactConstruct();
+ }
}
+ return super.getConstructor(node);
+ }
- protected Construct createCompactConstruct() {
- return new ConstructCompactObject();
- }
+ public class ConstructCompactObject extends ConstructMapping {
@Override
- protected Construct getConstructor(Node node) {
- if (node instanceof MappingNode) {
- MappingNode mnode = (MappingNode) node;
- List<NodeTuple> list = mnode.getValue();
- if (list.size() == 1) {
- NodeTuple tuple = list.get(0);
- Node key = tuple.getKeyNode();
- if (key instanceof ScalarNode) {
- ScalarNode scalar = (ScalarNode) key;
- if (GUESS_COMPACT.matcher(scalar.getValue()).matches()) {
- return getCompactConstruct();
- }
- }
- }
- } else if (node instanceof ScalarNode) {
- ScalarNode scalar = (ScalarNode) node;
- if (GUESS_COMPACT.matcher(scalar.getValue()).matches()) {
- return getCompactConstruct();
- }
- }
- return super.getConstructor(node);
+ public void construct2ndStep(Node node, Object object) {
+ // Compact Object Notation may contain only one entry
+ MappingNode mnode = (MappingNode) node;
+ NodeTuple nodeTuple = mnode.getValue().iterator().next();
+
+ Node valueNode = nodeTuple.getValueNode();
+
+ if (valueNode instanceof MappingNode) {
+ valueNode.setType(object.getClass());
+ constructJavaBean2ndStep((MappingNode) valueNode, object);
+ } else {
+ // value is a list
+ applySequence(object, constructSequence((SequenceNode) valueNode));
+ }
}
- public class ConstructCompactObject extends ConstructMapping {
-
- @Override
- public void construct2ndStep(Node node, Object object) {
- // Compact Object Notation may contain only one entry
- MappingNode mnode = (MappingNode) node;
- NodeTuple nodeTuple = mnode.getValue().iterator().next();
-
- Node valueNode = nodeTuple.getValueNode();
-
- if (valueNode instanceof MappingNode) {
- valueNode.setType(object.getClass());
- constructJavaBean2ndStep((MappingNode) valueNode, object);
- } else {
- // value is a list
- applySequence(object, constructSequence((SequenceNode) valueNode));
- }
- }
-
- /*
- * MappingNode and ScalarNode end up here only they assumed to be a
- * compact object's representation (@see getConstructor(Node) above)
- */
- public Object construct(Node node) {
- ScalarNode tmpNode = null;
- if (node instanceof MappingNode) {
- // Compact Object Notation may contain only one entry
- MappingNode mnode = (MappingNode) node;
- NodeTuple nodeTuple = mnode.getValue().iterator().next();
- node.setTwoStepsConstruction(true);
- tmpNode = (ScalarNode) nodeTuple.getKeyNode();
- // return constructScalar((ScalarNode) keyNode);
- } else {
- tmpNode = (ScalarNode) node;
- }
-
- CompactData data = getCompactData(tmpNode.getValue());
- if (data == null) { // TODO: Should we throw an exception here ?
- return constructScalar(tmpNode);
- }
- return constructCompactFormat(tmpNode, data);
- }
+ /*
+ * MappingNode and ScalarNode end up here only they assumed to be a compact object's
+ * representation (@see getConstructor(Node) above)
+ */
+ public Object construct(Node node) {
+ ScalarNode tmpNode;
+ if (node instanceof MappingNode) {
+ // Compact Object Notation may contain only one entry
+ MappingNode mnode = (MappingNode) node;
+ NodeTuple nodeTuple = mnode.getValue().iterator().next();
+ node.setTwoStepsConstruction(true);
+ tmpNode = (ScalarNode) nodeTuple.getKeyNode();
+ // return constructScalar((ScalarNode) keyNode);
+ } else {
+ tmpNode = (ScalarNode) node;
+ }
+
+ CompactData data = getCompactData(tmpNode.getValue());
+ if (data == null) { // TODO: Should we throw an exception here ?
+ return constructScalar(tmpNode);
+ }
+ return constructCompactFormat(tmpNode, data);
}
-
- protected void applySequence(Object bean, List<?> value) {
- try {
- Property property = getPropertyUtils().getProperty(bean.getClass(),
- getSequencePropertyName(bean.getClass()));
- property.set(bean, value);
- } catch (Exception e) {
- throw new YAMLException(e);
- }
+ }
+
+ protected void applySequence(Object bean, List<?> value) {
+ try {
+ Property property =
+ getPropertyUtils().getProperty(bean.getClass(), getSequencePropertyName(bean.getClass()));
+ property.set(bean, value);
+ } catch (Exception e) {
+ throw new YAMLException(e);
}
-
- /**
- * Provide the name of the property which is used when the entries form a
- * sequence. The property must be a List.
- *
- * @throws IntrospectionException
- */
- protected String getSequencePropertyName(Class<?> bean) throws IntrospectionException {
- Set<Property> properties = getPropertyUtils().getProperties(bean);
- for (Iterator<Property> iterator = properties.iterator(); iterator.hasNext();) {
- Property property = iterator.next();
- if (!List.class.isAssignableFrom(property.getType())) {
- iterator.remove();
- }
- }
- if (properties.size() == 0) {
- throw new YAMLException("No list property found in " + bean);
- } else if (properties.size() > 1) {
- throw new YAMLException(
- "Many list properties found in "
- + bean
- + "; Please override getSequencePropertyName() to specify which property to use.");
- }
- return properties.iterator().next().getName();
+ }
+
+ /**
+ * Provide the name of the property which is used when the entries form a sequence. The property
+ * must be a List.
+ *
+ * @param bean the class to provide exactly one List property
+ * @return name of the List property
+ */
+ protected String getSequencePropertyName(Class<?> bean) {
+ Set<Property> properties = getPropertyUtils().getProperties(bean);
+ for (Iterator<Property> iterator = properties.iterator(); iterator.hasNext();) {
+ Property property = iterator.next();
+ if (!List.class.isAssignableFrom(property.getType())) {
+ iterator.remove();
+ }
+ }
+ if (properties.size() == 0) {
+ throw new YAMLException("No list property found in " + bean);
+ } else if (properties.size() > 1) {
+ throw new YAMLException("Many list properties found in " + bean
+ + "; Please override getSequencePropertyName() to specify which property to use.");
}
+ return properties.iterator().next().getName();
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactData.java b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactData.java
index c2da7c46..13126d80 100644
--- a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactData.java
+++ b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/CompactData.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.extensions.compactnotation;
@@ -21,28 +19,29 @@ import java.util.List;
import java.util.Map;
public class CompactData {
- private String prefix;
- private List<String> arguments = new ArrayList<String>();
- private Map<String, String> properties = new HashMap<String, String>();
-
- public CompactData(String prefix) {
- this.prefix = prefix;
- }
-
- public String getPrefix() {
- return prefix;
- }
-
- public Map<String, String> getProperties() {
- return properties;
- }
-
- public List<String> getArguments() {
- return arguments;
- }
-
- @Override
- public String toString() {
- return "CompactData: " + prefix + " " + properties;
- }
-} \ No newline at end of file
+
+ private final String prefix;
+ private final List<String> arguments = new ArrayList<String>();
+ private final Map<String, String> properties = new HashMap<String, String>();
+
+ public CompactData(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+ @Override
+ public String toString() {
+ return "CompactData: " + prefix + " " + properties;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/PackageCompactConstructor.java b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/PackageCompactConstructor.java
index e58c3add..e6b1a31a 100644
--- a/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/PackageCompactConstructor.java
+++ b/src/main/java/org/yaml/snakeyaml/extensions/compactnotation/PackageCompactConstructor.java
@@ -1,37 +1,36 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.extensions.compactnotation;
public class PackageCompactConstructor extends CompactConstructor {
- private String packageName;
- public PackageCompactConstructor(String packageName) {
- this.packageName = packageName;
- }
+ private final String packageName;
+
+ public PackageCompactConstructor(String packageName) {
+ this.packageName = packageName;
+ }
- @Override
- protected Class<?> getClassForName(String name) throws ClassNotFoundException {
- if (name.indexOf('.') < 0) {
- try {
- Class<?> clazz = Class.forName(packageName + "." + name);
- return clazz;
- } catch (ClassNotFoundException e) {
- // use super implementation
- }
- }
- return super.getClassForName(name);
+ @Override
+ protected Class<?> getClassForName(String name) throws ClassNotFoundException {
+ if (name.indexOf('.') < 0) {
+ try {
+ Class<?> clazz = Class.forName(packageName + "." + name);
+ return clazz;
+ } catch (ClassNotFoundException e) {
+ // use super implementation
+ }
}
+ return super.getClassForName(name);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/external/biz/base64Coder/Base64Coder.java b/src/main/java/org/yaml/snakeyaml/external/biz/base64Coder/Base64Coder.java
index 65923b65..db43b474 100644
--- a/src/main/java/org/yaml/snakeyaml/external/biz/base64Coder/Base64Coder.java
+++ b/src/main/java/org/yaml/snakeyaml/external/biz/base64Coder/Base64Coder.java
@@ -4,11 +4,11 @@
// This module is multi-licensed and may be used under the terms
// of any of the following licenses:
//
-// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
-// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
-// GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
-// AL, Apache License, V2.0 or later, http://www.apache.org/licenses
-// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
+// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
+// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
+// GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
+// AL, Apache License, V2.0 or later, http://www.apache.org/licenses
+// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.
@@ -17,289 +17,265 @@ package org.yaml.snakeyaml.external.biz.base64Coder;
/**
* A Base64 encoder/decoder.
- *
+ *
* <p>
- * This class is used to encode and decode data in Base64 format as described in
- * RFC 1521.
- *
+ * This class is used to encode and decode data in Base64 format as described in RFC 1521.
+ *
* <p>
- * Project home page: <a
- * href="http://www.source-code.biz/base64coder/java/">www.
+ * Project home page: <a href="http://www.source-code.biz/base64coder/java/">www.
* source-code.biz/base64coder/java</a><br>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
*/
public class Base64Coder {
- // The line separator string of the operating system.
- private static final String systemLineSeparator = System.getProperty("line.separator");
+ // The line separator string of the operating system.
+ private static final String systemLineSeparator = System.getProperty("line.separator");
- // Mapping table from 6-bit nibbles to Base64 characters.
- private static char[] map1 = new char[64];
- static {
- int i = 0;
- for (char c = 'A'; c <= 'Z'; c++)
- map1[i++] = c;
- for (char c = 'a'; c <= 'z'; c++)
- map1[i++] = c;
- for (char c = '0'; c <= '9'; c++)
- map1[i++] = c;
- map1[i++] = '+';
- map1[i++] = '/';
- }
+ // Mapping table from 6-bit nibbles to Base64 characters.
+ private static final char[] map1 = new char[64];
- // Mapping table from Base64 characters to 6-bit nibbles.
- private static byte[] map2 = new byte[128];
- static {
- for (int i = 0; i < map2.length; i++)
- map2[i] = -1;
- for (int i = 0; i < 64; i++)
- map2[map1[i]] = (byte) i;
+ static {
+ int i = 0;
+ for (char c = 'A'; c <= 'Z'; c++) {
+ map1[i++] = c;
}
-
- /**
- * Encodes a string into Base64 format. No blanks or line breaks are
- * inserted.
- *
- * @param s
- * A String to be encoded.
- * @return A String containing the Base64 encoded data.
- */
- public static String encodeString(String s) {
- return new String(encode(s.getBytes()));
+ for (char c = 'a'; c <= 'z'; c++) {
+ map1[i++] = c;
}
-
- /**
- * Encodes a byte array into Base 64 format and breaks the output into lines
- * of 76 characters. This method is compatible with
- * <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>.
- *
- * @param in
- * An array containing the data bytes to be encoded.
- * @return A String containing the Base64 encoded data, broken into lines.
- */
- public static String encodeLines(byte[] in) {
- return encodeLines(in, 0, in.length, 76, systemLineSeparator);
+ for (char c = '0'; c <= '9'; c++) {
+ map1[i++] = c;
}
+ map1[i++] = '+';
+ map1[i++] = '/';
+ }
- /**
- * Encodes a byte array into Base 64 format and breaks the output into
- * lines.
- *
- * @param in
- * An array containing the data bytes to be encoded.
- * @param iOff
- * Offset of the first byte in <code>in</code> to be processed.
- * @param iLen
- * Number of bytes to be processed in <code>in</code>, starting
- * at <code>iOff</code>.
- * @param lineLen
- * Line length for the output data. Should be a multiple of 4.
- * @param lineSeparator
- * The line separator to be used to separate the output lines.
- * @return A String containing the Base64 encoded data, broken into lines.
- */
- public static String encodeLines(byte[] in, int iOff, int iLen, int lineLen,
- String lineSeparator) {
- int blockLen = (lineLen * 3) / 4;
- if (blockLen <= 0)
- throw new IllegalArgumentException();
- int lines = (iLen + blockLen - 1) / blockLen;
- int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();
- StringBuilder buf = new StringBuilder(bufLen);
- int ip = 0;
- while (ip < iLen) {
- int l = Math.min(iLen - ip, blockLen);
- buf.append(encode(in, iOff + ip, l));
- buf.append(lineSeparator);
- ip += l;
- }
- return buf.toString();
- }
+ // Mapping table from Base64 characters to 6-bit nibbles.
+ private static final byte[] map2 = new byte[128];
- /**
- * Encodes a byte array into Base64 format. No blanks or line breaks are
- * inserted in the output.
- *
- * @param in
- * An array containing the data bytes to be encoded.
- * @return A character array containing the Base64 encoded data.
- */
- public static char[] encode(byte[] in) {
- return encode(in, 0, in.length);
+ static {
+ for (int i = 0; i < map2.length; i++) {
+ map2[i] = -1;
}
-
- /**
- * Encodes a byte array into Base64 format. No blanks or line breaks are
- * inserted in the output.
- *
- * @param in
- * An array containing the data bytes to be encoded.
- * @param iLen
- * Number of bytes to process in <code>in</code>.
- * @return A character array containing the Base64 encoded data.
- */
- public static char[] encode(byte[] in, int iLen) {
- return encode(in, 0, iLen);
+ for (int i = 0; i < 64; i++) {
+ map2[map1[i]] = (byte) i;
}
+ }
- /**
- * Encodes a byte array into Base64 format. No blanks or line breaks are
- * inserted in the output.
- *
- * @param in
- * An array containing the data bytes to be encoded.
- * @param iOff
- * Offset of the first byte in <code>in</code> to be processed.
- * @param iLen
- * Number of bytes to process in <code>in</code>, starting at
- * <code>iOff</code>.
- * @return A character array containing the Base64 encoded data.
- */
- public static char[] encode(byte[] in, int iOff, int iLen) {
- int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
- int oLen = ((iLen + 2) / 3) * 4; // output length including padding
- char[] out = new char[oLen];
- int ip = iOff;
- int iEnd = iOff + iLen;
- int op = 0;
- while (ip < iEnd) {
- int i0 = in[ip++] & 0xff;
- int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
- int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
- int o0 = i0 >>> 2;
- int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
- int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
- int o3 = i2 & 0x3F;
- out[op++] = map1[o0];
- out[op++] = map1[o1];
- out[op] = op < oDataLen ? map1[o2] : '=';
- op++;
- out[op] = op < oDataLen ? map1[o3] : '=';
- op++;
- }
- return out;
- }
+ /**
+ * Encodes a string into Base64 format. No blanks or line breaks are inserted.
+ *
+ * @param s A String to be encoded.
+ * @return A String containing the Base64 encoded data.
+ */
+ public static String encodeString(String s) {
+ return new String(encode(s.getBytes()));
+ }
- /**
- * Decodes a string from Base64 format. No blanks or line breaks are allowed
- * within the Base64 encoded input data.
- *
- * @param s
- * A Base64 String to be decoded.
- * @return A String containing the decoded data.
- * @throws IllegalArgumentException
- * If the input is not valid Base64 encoded data.
- */
- public static String decodeString(String s) {
- return new String(decode(s));
- }
+ /**
+ * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
+ * This method is compatible with <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>.
+ *
+ * @param in An array containing the data bytes to be encoded.
+ * @return A String containing the Base64 encoded data, broken into lines.
+ */
+ public static String encodeLines(byte[] in) {
+ return encodeLines(in, 0, in.length, 76, systemLineSeparator);
+ }
- /**
- * Decodes a byte array from Base64 format and ignores line separators, tabs
- * and blanks. CR, LF, Tab and Space characters are ignored in the input
- * data. This method is compatible with
- * <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>.
- *
- * @param s
- * A Base64 String to be decoded.
- * @return An array containing the decoded data bytes.
- * @throws IllegalArgumentException
- * If the input is not valid Base64 encoded data.
- */
- public static byte[] decodeLines(String s) {
- char[] buf = new char[s.length()];
- int p = 0;
- for (int ip = 0; ip < s.length(); ip++) {
- char c = s.charAt(ip);
- if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
- buf[p++] = c;
- }
- return decode(buf, 0, p);
+ /**
+ * Encodes a byte array into Base 64 format and breaks the output into lines.
+ *
+ * @param in An array containing the data bytes to be encoded.
+ * @param iOff Offset of the first byte in <code>in</code> to be processed.
+ * @param iLen Number of bytes to be processed in <code>in</code>, starting at <code>iOff</code>.
+ * @param lineLen Line length for the output data. Should be a multiple of 4.
+ * @param lineSeparator The line separator to be used to separate the output lines.
+ * @return A String containing the Base64 encoded data, broken into lines.
+ */
+ public static String encodeLines(byte[] in, int iOff, int iLen, int lineLen,
+ String lineSeparator) {
+ int blockLen = (lineLen * 3) / 4;
+ if (blockLen <= 0) {
+ throw new IllegalArgumentException();
}
-
- /**
- * Decodes a byte array from Base64 format. No blanks or line breaks are
- * allowed within the Base64 encoded input data.
- *
- * @param s
- * A Base64 String to be decoded.
- * @return An array containing the decoded data bytes.
- * @throws IllegalArgumentException
- * If the input is not valid Base64 encoded data.
- */
- public static byte[] decode(String s) {
- return decode(s.toCharArray());
+ int lines = (iLen + blockLen - 1) / blockLen;
+ int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();
+ StringBuilder buf = new StringBuilder(bufLen);
+ int ip = 0;
+ while (ip < iLen) {
+ int l = Math.min(iLen - ip, blockLen);
+ buf.append(encode(in, iOff + ip, l));
+ buf.append(lineSeparator);
+ ip += l;
}
+ return buf.toString();
+ }
- /**
- * Decodes a byte array from Base64 format. No blanks or line breaks are
- * allowed within the Base64 encoded input data.
- *
- * @param in
- * A character array containing the Base64 encoded data.
- * @return An array containing the decoded data bytes.
- * @throws IllegalArgumentException
- * If the input is not valid Base64 encoded data.
- */
- public static byte[] decode(char[] in) {
- return decode(in, 0, in.length);
+ /**
+ * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output.
+ *
+ * @param in An array containing the data bytes to be encoded.
+ * @return A character array containing the Base64 encoded data.
+ */
+ public static char[] encode(byte[] in) {
+ return encode(in, 0, in.length);
+ }
+
+ /**
+ * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output.
+ *
+ * @param in An array containing the data bytes to be encoded.
+ * @param iLen Number of bytes to process in <code>in</code>.
+ * @return A character array containing the Base64 encoded data.
+ */
+ public static char[] encode(byte[] in, int iLen) {
+ return encode(in, 0, iLen);
+ }
+
+ /**
+ * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output.
+ *
+ * @param in An array containing the data bytes to be encoded.
+ * @param iOff Offset of the first byte in <code>in</code> to be processed.
+ * @param iLen Number of bytes to process in <code>in</code>, starting at <code>iOff</code>.
+ * @return A character array containing the Base64 encoded data.
+ */
+ public static char[] encode(byte[] in, int iOff, int iLen) {
+ int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
+ int oLen = ((iLen + 2) / 3) * 4; // output length including padding
+ char[] out = new char[oLen];
+ int ip = iOff;
+ int iEnd = iOff + iLen;
+ int op = 0;
+ while (ip < iEnd) {
+ int i0 = in[ip++] & 0xff;
+ int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
+ int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
+ int o0 = i0 >>> 2;
+ int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
+ int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+ int o3 = i2 & 0x3F;
+ out[op++] = map1[o0];
+ out[op++] = map1[o1];
+ out[op] = op < oDataLen ? map1[o2] : '=';
+ op++;
+ out[op] = op < oDataLen ? map1[o3] : '=';
+ op++;
}
+ return out;
+ }
- /**
- * Decodes a byte array from Base64 format. No blanks or line breaks are
- * allowed within the Base64 encoded input data.
- *
- * @param in
- * A character array containing the Base64 encoded data.
- * @param iOff
- * Offset of the first character in <code>in</code> to be
- * processed.
- * @param iLen
- * Number of characters to process in <code>in</code>, starting
- * at <code>iOff</code>.
- * @return An array containing the decoded data bytes.
- * @throws IllegalArgumentException
- * If the input is not valid Base64 encoded data.
- */
- public static byte[] decode(char[] in, int iOff, int iLen) {
- if (iLen % 4 != 0)
- throw new IllegalArgumentException(
- "Length of Base64 encoded input string is not a multiple of 4.");
- while (iLen > 0 && in[iOff + iLen - 1] == '=')
- iLen--;
- int oLen = (iLen * 3) / 4;
- byte[] out = new byte[oLen];
- int ip = iOff;
- int iEnd = iOff + iLen;
- int op = 0;
- while (ip < iEnd) {
- int i0 = in[ip++];
- int i1 = in[ip++];
- int i2 = ip < iEnd ? in[ip++] : 'A';
- int i3 = ip < iEnd ? in[ip++] : 'A';
- if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
- throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
- int b0 = map2[i0];
- int b1 = map2[i1];
- int b2 = map2[i2];
- int b3 = map2[i3];
- if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
- throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
- int o0 = (b0 << 2) | (b1 >>> 4);
- int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
- int o2 = ((b2 & 3) << 6) | b3;
- out[op++] = (byte) o0;
- if (op < oLen)
- out[op++] = (byte) o1;
- if (op < oLen)
- out[op++] = (byte) o2;
- }
- return out;
+ /**
+ * Decodes a string from Base64 format. No blanks or line breaks are allowed within the Base64
+ * encoded input data.
+ *
+ * @param s A Base64 String to be decoded.
+ * @return A String containing the decoded data.
+ * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
+ */
+ public static String decodeString(String s) {
+ return new String(decode(s));
+ }
+
+ /**
+ * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks. CR, LF,
+ * Tab and Space characters are ignored in the input data. This method is compatible with
+ * <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>.
+ *
+ * @param s A Base64 String to be decoded.
+ * @return An array containing the decoded data bytes.
+ * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
+ */
+ public static byte[] decodeLines(String s) {
+ char[] buf = new char[s.length()];
+ int p = 0;
+ for (int ip = 0; ip < s.length(); ip++) {
+ char c = s.charAt(ip);
+ if (c != ' ' && c != '\r' && c != '\n' && c != '\t') {
+ buf[p++] = c;
+ }
}
+ return decode(buf, 0, p);
+ }
+
+ /**
+ * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64
+ * encoded input data.
+ *
+ * @param s A Base64 String to be decoded.
+ * @return An array containing the decoded data bytes.
+ * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
+ */
+ public static byte[] decode(String s) {
+ return decode(s.toCharArray());
+ }
- // Dummy constructor.
- private Base64Coder() {
+ /**
+ * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64
+ * encoded input data.
+ *
+ * @param in A character array containing the Base64 encoded data.
+ * @return An array containing the decoded data bytes.
+ * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
+ */
+ public static byte[] decode(char[] in) {
+ return decode(in, 0, in.length);
+ }
+
+ /**
+ * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64
+ * encoded input data.
+ *
+ * @param in A character array containing the Base64 encoded data.
+ * @param iOff Offset of the first character in <code>in</code> to be processed.
+ * @param iLen Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
+ * @return An array containing the decoded data bytes.
+ * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
+ */
+ public static byte[] decode(char[] in, int iOff, int iLen) {
+ if (iLen % 4 != 0) {
+ throw new IllegalArgumentException(
+ "Length of Base64 encoded input string is not a multiple of 4.");
+ }
+ while (iLen > 0 && in[iOff + iLen - 1] == '=') {
+ iLen--;
}
+ int oLen = (iLen * 3) / 4;
+ byte[] out = new byte[oLen];
+ int ip = iOff;
+ int iEnd = iOff + iLen;
+ int op = 0;
+ while (ip < iEnd) {
+ int i0 = in[ip++];
+ int i1 = in[ip++];
+ int i2 = ip < iEnd ? in[ip++] : 'A';
+ int i3 = ip < iEnd ? in[ip++] : 'A';
+ if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) {
+ throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
+ }
+ int b0 = map2[i0];
+ int b1 = map2[i1];
+ int b2 = map2[i2];
+ int b3 = map2[i3];
+ if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) {
+ throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
+ }
+ int o0 = (b0 << 2) | (b1 >>> 4);
+ int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
+ int o2 = ((b2 & 3) << 6) | b3;
+ out[op++] = (byte) o0;
+ if (op < oLen) {
+ out[op++] = (byte) o1;
+ }
+ if (op < oLen) {
+ out[op++] = (byte) o2;
+ }
+ }
+ return out;
+ }
+
+ // Dummy constructor.
+ private Base64Coder() {}
} // end class Base64Coder
diff --git a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/Escaper.java b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/Escaper.java
index c26e3cb6..a94f6154 100644
--- a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/Escaper.java
+++ b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/Escaper.java
@@ -1,97 +1,82 @@
-/* Copyright (c) 2008 Google Inc.
+/*
+ * Copyright (c) 2008 Google Inc.
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.external.com.google.gdata.util.common.base;
/**
- * An object that converts literal text into a format safe for inclusion in a
- * particular context (such as an XML document). Typically (but not always), the
- * inverse process of "unescaping" the text is performed automatically by the
- * relevant parser.
- *
+ * An object that converts literal text into a format safe for inclusion in a particular context
+ * (such as an XML document). Typically (but not always), the inverse process of "unescaping" the
+ * text is performed automatically by the relevant parser.
+ *
* <p>
- * For example, an XML escaper would convert the literal string
- * {@code "Foo<Bar>"} into {@code "Foo&lt;Bar&gt;"} to prevent {@code "<Bar>"}
- * from being confused with an XML tag. When the resulting XML document is
- * parsed, the parser API will return this text as the original literal string
- * {@code "Foo<Bar>"}.
- *
+ * For example, an XML escaper would convert the literal string {@code "Foo<Bar>"} into
+ * {@code "Foo&lt;Bar&gt;"} to prevent {@code "<Bar>"} from being confused with an XML tag. When the
+ * resulting XML document is parsed, the parser API will return this text as the original literal
+ * string {@code "Foo<Bar>"}.
+ *
* <p>
- * An {@code Escaper} instance is required to be stateless, and safe when used
- * concurrently by multiple threads.
- *
+ * An {@code Escaper} instance is required to be stateless, and safe when used concurrently by
+ * multiple threads.
+ *
* <p>
- * Several popular escapers are defined as constants in the class
- * {@link CharEscapers}. To create your own escapers, use
- * {@link CharEscaperBuilder}, or extend {@link CharEscaper} or
+ * Several popular escapers are defined as constants in the class {@link CharEscapers}. To create
+ * your own escapers, use {@link CharEscaperBuilder}, or extend {@link CharEscaper} or
* {@code UnicodeEscaper}.
- *
- *
*/
public interface Escaper {
- /**
- * Returns the escaped form of a given literal string.
- *
- * <p>
- * Note that this method may treat input characters differently depending on
- * the specific escaper implementation.
- * <ul>
- * <li>{@link UnicodeEscaper} handles <a
- * href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> correctly,
- * including surrogate character pairs. If the input is badly formed the
- * escaper should throw {@link IllegalArgumentException}.
- * <li>{@link CharEscaper} handles Java characters independently and does
- * not verify the input for well formed characters. A CharEscaper should not
- * be used in situations where input is not guaranteed to be restricted to
- * the Basic Multilingual Plane (BMP).
- * </ul>
- *
- * @param string
- * the literal string to be escaped
- * @return the escaped form of {@code string}
- * @throws NullPointerException
- * if {@code string} is null
- * @throws IllegalArgumentException
- * if {@code string} contains badly formed UTF-16 or cannot be
- * escaped for any other reason
- */
- public String escape(String string);
- /**
- * Returns an {@code Appendable} instance which automatically escapes all
- * text appended to it before passing the resulting text to an underlying
- * {@code Appendable}.
- *
- * <p>
- * Note that this method may treat input characters differently depending on
- * the specific escaper implementation.
- * <ul>
- * <li>{@link UnicodeEscaper} handles <a
- * href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> correctly,
- * including surrogate character pairs. If the input is badly formed the
- * escaper should throw {@link IllegalArgumentException}.
- * <li>{@link CharEscaper} handles Java characters independently and does
- * not verify the input for well formed characters. A CharEscaper should not
- * be used in situations where input is not guaranteed to be restricted to
- * the Basic Multilingual Plane (BMP).
- * </ul>
- *
- * @param out
- * the underlying {@code Appendable} to append escaped output to
- * @return an {@code Appendable} which passes text to {@code out} after
- * escaping it.
- */
- public Appendable escape(Appendable out);
+ /**
+ * Returns the escaped form of a given literal string.
+ *
+ * <p>
+ * Note that this method may treat input characters differently depending on the specific escaper
+ * implementation.
+ * <ul>
+ * <li>{@link UnicodeEscaper} handles <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
+ * correctly, including surrogate character pairs. If the input is badly formed the escaper should
+ * throw {@link IllegalArgumentException}.
+ * <li>{@link CharEscaper} handles Java characters independently and does not verify the input for
+ * well formed characters. A CharEscaper should not be used in situations where input is not
+ * guaranteed to be restricted to the Basic Multilingual Plane (BMP).
+ * </ul>
+ *
+ * @param string the literal string to be escaped
+ * @return the escaped form of {@code string}
+ * @throws NullPointerException if {@code string} is null
+ * @throws IllegalArgumentException if {@code string} contains badly formed UTF-16 or cannot be
+ * escaped for any other reason
+ */
+ String escape(String string);
+
+ /**
+ * Returns an {@code Appendable} instance which automatically escapes all text appended to it
+ * before passing the resulting text to an underlying {@code Appendable}.
+ *
+ * <p>
+ * Note that this method may treat input characters differently depending on the specific escaper
+ * implementation.
+ * <ul>
+ * <li>{@link UnicodeEscaper} handles <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
+ * correctly, including surrogate character pairs. If the input is badly formed the escaper should
+ * throw {@link IllegalArgumentException}.
+ * <li>{@link CharEscaper} handles Java characters independently and does not verify the input for
+ * well formed characters. A CharEscaper should not be used in situations where input is not
+ * guaranteed to be restricted to the Basic Multilingual Plane (BMP).
+ * </ul>
+ *
+ * @param out the underlying {@code Appendable} to append escaped output to
+ * @return an {@code Appendable} which passes text to {@code out} after escaping it.
+ */
+ Appendable escape(Appendable out);
}
diff --git a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/PercentEscaper.java b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/PercentEscaper.java
index 5e2f902d..f115a2cc 100644
--- a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/PercentEscaper.java
+++ b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/PercentEscaper.java
@@ -1,281 +1,267 @@
-/* Copyright (c) 2008 Google Inc.
+/*
+ * Copyright (c) 2008 Google Inc.
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.external.com.google.gdata.util.common.base;
/**
- * A {@code UnicodeEscaper} that escapes some set of Java characters using the
- * URI percent encoding scheme. The set of safe characters (those which remain
- * unescaped) can be specified on construction.
- *
+ * A {@code UnicodeEscaper} that escapes some set of Java characters using the URI percent encoding
+ * scheme. The set of safe characters (those which remain unescaped) can be specified on
+ * construction.
+ *
* <p>
- * For details on escaping URIs for use in web pages, see section 2.4 of <a
- * href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
- *
+ * For details on escaping URIs for use in web pages, see section 2.4 of
+ * <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
+ *
* <p>
- * In most cases this class should not need to be used directly. If you have no
- * special requirements for escaping your URIs, you should use either
- * {@link CharEscapers#uriEscaper()} or {@link CharEscapers#uriEscaper(boolean)}.
- *
+ * In most cases this class should not need to be used directly. If you have no special requirements
+ * for escaping your URIs, you should use either {@link CharEscapers#uriEscaper()} or
+ * {@link CharEscapers#uriEscaper(boolean)}.
+ *
* <p>
* When encoding a String, the following rules apply:
* <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
+ * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain the
+ * same.
* <li>Any additionally specified safe characters remain the same.
- * <li>If {@code plusForSpace} was specified, the space character " " is
- * converted into a plus sign "+".
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string "%XY",
- * where "XY" is the two-digit, uppercase, hexadecimal representation of the
- * byte value.
+ * <li>If {@code plusForSpace} was specified, the space character " " is converted into a plus sign
+ * "+".
+ * <li>All other characters are converted into one or more bytes using UTF-8 encoding and each byte
+ * is then represented by the 3-character string "%XY", where "XY" is the two-digit, uppercase,
+ * hexadecimal representation of the byte value.
* </ul>
- *
+ *
* <p>
- * RFC 2396 specifies the set of unreserved characters as "-", "_", ".", "!",
- * "~", "*", "'", "(" and ")". It goes on to state:
- *
+ * RFC 2396 specifies the set of unreserved characters as "-", "_", ".", "!", "~", "*", "'", "(" and
+ * ")". It goes on to state:
+ *
* <p>
- * <i>Unreserved characters can be escaped without changing the semantics of the
- * URI, but this should not be done unless the URI is being used in a context
- * that does not allow the unescaped character to appear.</i>
- *
+ * <i>Unreserved characters can be escaped without changing the semantics of the URI, but this
+ * should not be done unless the URI is being used in a context that does not allow the unescaped
+ * character to appear.</i>
+ *
* <p>
- * For performance reasons the only currently supported character encoding of
- * this class is UTF-8.
- *
+ * For performance reasons the only currently supported character encoding of this class is UTF-8.
+ *
* <p>
- * <b>Note</b>: This escaper produces uppercase hexidecimal sequences. From <a
- * href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits for
- * all percent-encodings."</i>
- *
- *
+ * <b>Note</b>: This escaper produces uppercase hexidecimal sequences. From
+ * <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>:<br>
+ * <i>"URI producers and normalizers should use uppercase hexadecimal digits for all
+ * percent-encodings."</i>
*/
public class PercentEscaper extends UnicodeEscaper {
- /**
- * A string of safe characters that mimics the behavior of
- * {@link java.net.URLEncoder}.
- *
- */
- public static final String SAFECHARS_URLENCODER = "-_.*";
- /**
- * A string of characters that do not need to be encoded when used in URI
- * path segments, as specified in RFC 3986. Note that some of these
- * characters do need to be escaped when used in other parts of the URI.
- */
- public static final String SAFEPATHCHARS_URLENCODER = "-_.!~*'()@:$&,;=";
+ /**
+ * A string of safe characters that mimics the behavior of {@link java.net.URLEncoder}.
+ */
+ public static final String SAFECHARS_URLENCODER = "-_.*";
- /**
- * A string of characters that do not need to be encoded when used in URI
- * query strings, as specified in RFC 3986. Note that some of these
- * characters do need to be escaped when used in other parts of the URI.
- */
- public static final String SAFEQUERYSTRINGCHARS_URLENCODER = "-_.!~*'()@:$,;/?:";
+ /**
+ * A string of characters that do not need to be encoded when used in URI path segments, as
+ * specified in RFC 3986. Note that some of these characters do need to be escaped when used in
+ * other parts of the URI.
+ */
+ public static final String SAFEPATHCHARS_URLENCODER = "-_.!~*'()@:$&,;=";
- // In some uri escapers spaces are escaped to '+'
- private static final char[] URI_ESCAPED_SPACE = { '+' };
+ /**
+ * A string of characters that do not need to be encoded when used in URI query strings, as
+ * specified in RFC 3986. Note that some of these characters do need to be escaped when used in
+ * other parts of the URI.
+ */
+ public static final String SAFEQUERYSTRINGCHARS_URLENCODER = "-_.!~*'()@:$,;/?:";
- private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+ // In some uri escapers spaces are escaped to '+'
+ private static final char[] URI_ESCAPED_SPACE = {'+'};
- /**
- * If true we should convert space to the {@code +} character.
- */
- private final boolean plusForSpace;
+ private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray();
- /**
- * An array of flags where for any {@code char c} if {@code safeOctets[c]}
- * is true then {@code c} should remain unmodified in the output. If
- * {@code c > safeOctets.length} then it should be escaped.
- */
- private final boolean[] safeOctets;
+ /**
+ * If true we should convert space to the {@code +} character.
+ */
+ private final boolean plusForSpace;
- /**
- * Constructs a URI escaper with the specified safe characters and optional
- * handling of the space character.
- *
- * @param safeChars
- * a non null string specifying additional safe characters for
- * this escaper (the ranges 0..9, a..z and A..Z are always safe
- * and should not be specified here)
- * @param plusForSpace
- * true if ASCII space should be escaped to {@code +} rather than
- * {@code %20}
- * @throws IllegalArgumentException
- * if any of the parameters were invalid
- */
- public PercentEscaper(String safeChars, boolean plusForSpace) {
- // Avoid any misunderstandings about the behavior of this escaper
- if (safeChars.matches(".*[0-9A-Za-z].*")) {
- throw new IllegalArgumentException(
- "Alphanumeric characters are always 'safe' and should not be "
- + "explicitly specified");
- }
- // Avoid ambiguous parameters. Safe characters are never modified so if
- // space is a safe character then setting plusForSpace is meaningless.
- if (plusForSpace && safeChars.contains(" ")) {
- throw new IllegalArgumentException(
- "plusForSpace cannot be specified when space is a 'safe' character");
- }
- if (safeChars.contains("%")) {
- throw new IllegalArgumentException("The '%' character cannot be specified as 'safe'");
- }
- this.plusForSpace = plusForSpace;
- this.safeOctets = createSafeOctets(safeChars);
+ /**
+ * An array of flags where for any {@code char c} if {@code safeOctets[c]} is true then {@code c}
+ * should remain unmodified in the output. If {@code c > safeOctets.length} then it should be
+ * escaped.
+ */
+ private final boolean[] safeOctets;
+
+ /**
+ * Constructs a URI escaper with the specified safe characters and optional handling of the space
+ * character.
+ *
+ * @param safeChars a non null string specifying additional safe characters for this escaper (the
+ * ranges 0..9, a..z and A..Z are always safe and should not be specified here)
+ * @param plusForSpace true if ASCII space should be escaped to {@code +} rather than {@code %20}
+ * @throws IllegalArgumentException if any of the parameters were invalid
+ */
+ public PercentEscaper(String safeChars, boolean plusForSpace) {
+ // Avoid any misunderstandings about the behavior of this escaper
+ if (safeChars.matches(".*[0-9A-Za-z].*")) {
+ throw new IllegalArgumentException(
+ "Alphanumeric characters are always 'safe' and should not be " + "explicitly specified");
+ }
+ // Avoid ambiguous parameters. Safe characters are never modified so if
+ // space is a safe character then setting plusForSpace is meaningless.
+ if (plusForSpace && safeChars.contains(" ")) {
+ throw new IllegalArgumentException(
+ "plusForSpace cannot be specified when space is a 'safe' character");
}
+ if (safeChars.contains("%")) {
+ throw new IllegalArgumentException("The '%' character cannot be specified as 'safe'");
+ }
+ this.plusForSpace = plusForSpace;
+ this.safeOctets = createSafeOctets(safeChars);
+ }
- /**
- * Creates a boolean[] with entries corresponding to the character values
- * for 0-9, A-Z, a-z and those specified in safeChars set to true. The array
- * is as small as is required to hold the given character information.
- */
- private static boolean[] createSafeOctets(String safeChars) {
- int maxChar = 'z';
- char[] safeCharArray = safeChars.toCharArray();
- for (char c : safeCharArray) {
- maxChar = Math.max(c, maxChar);
- }
- boolean[] octets = new boolean[maxChar + 1];
- for (int c = '0'; c <= '9'; c++) {
- octets[c] = true;
- }
- for (int c = 'A'; c <= 'Z'; c++) {
- octets[c] = true;
- }
- for (int c = 'a'; c <= 'z'; c++) {
- octets[c] = true;
- }
- for (char c : safeCharArray) {
- octets[c] = true;
- }
- return octets;
+ /**
+ * Creates a boolean[] with entries corresponding to the character values for 0-9, A-Z, a-z and
+ * those specified in safeChars set to true. The array is as small as is required to hold the
+ * given character information.
+ */
+ private static boolean[] createSafeOctets(String safeChars) {
+ int maxChar = 'z';
+ char[] safeCharArray = safeChars.toCharArray();
+ for (char c : safeCharArray) {
+ maxChar = Math.max(c, maxChar);
+ }
+ boolean[] octets = new boolean[maxChar + 1];
+ for (int c = '0'; c <= '9'; c++) {
+ octets[c] = true;
+ }
+ for (int c = 'A'; c <= 'Z'; c++) {
+ octets[c] = true;
+ }
+ for (int c = 'a'; c <= 'z'; c++) {
+ octets[c] = true;
+ }
+ for (char c : safeCharArray) {
+ octets[c] = true;
}
+ return octets;
+ }
- /*
- * Overridden for performance. For unescaped strings this improved the
- * performance of the uri escaper from ~760ns to ~400ns as measured by
- * {@link CharEscapersBenchmark}.
- */
- @Override
- protected int nextEscapeIndex(CharSequence csq, int index, int end) {
- for (; index < end; index++) {
- char c = csq.charAt(index);
- if (c >= safeOctets.length || !safeOctets[c]) {
- break;
- }
- }
- return index;
+ /*
+ * Overridden for performance. For unescaped strings this improved the performance of the uri
+ * escaper from ~760ns to ~400ns as measured by {@link CharEscapersBenchmark}.
+ */
+ @Override
+ protected int nextEscapeIndex(CharSequence csq, int index, int end) {
+ for (; index < end; index++) {
+ char c = csq.charAt(index);
+ if (c >= safeOctets.length || !safeOctets[c]) {
+ break;
+ }
}
+ return index;
+ }
- /*
- * Overridden for performance. For unescaped strings this improved the
- * performance of the uri escaper from ~400ns to ~170ns as measured by
- * {@link CharEscapersBenchmark}.
- */
- @Override
- public String escape(String s) {
- int slen = s.length();
- for (int index = 0; index < slen; index++) {
- char c = s.charAt(index);
- if (c >= safeOctets.length || !safeOctets[c]) {
- return escapeSlow(s, index);
- }
- }
- return s;
+ /*
+ * Overridden for performance. For unescaped strings this improved the performance of the uri
+ * escaper from ~400ns to ~170ns as measured by {@link CharEscapersBenchmark}.
+ */
+ @Override
+ public String escape(String s) {
+ int slen = s.length();
+ for (int index = 0; index < slen; index++) {
+ char c = s.charAt(index);
+ if (c >= safeOctets.length || !safeOctets[c]) {
+ return escapeSlow(s, index);
+ }
}
+ return s;
+ }
- /**
- * Escapes the given Unicode code point in UTF-8.
- */
- @Override
- protected char[] escape(int cp) {
- // We should never get negative values here but if we do it will throw
- // an
- // IndexOutOfBoundsException, so at least it will get spotted.
- if (cp < safeOctets.length && safeOctets[cp]) {
- return null;
- } else if (cp == ' ' && plusForSpace) {
- return URI_ESCAPED_SPACE;
- } else if (cp <= 0x7F) {
- // Single byte UTF-8 characters
- // Start with "%--" and fill in the blanks
- char[] dest = new char[3];
- dest[0] = '%';
- dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
- dest[1] = UPPER_HEX_DIGITS[cp >>> 4];
- return dest;
- } else if (cp <= 0x7ff) {
- // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff]
- // Start with "%--%--" and fill in the blanks
- char[] dest = new char[6];
- dest[0] = '%';
- dest[3] = '%';
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[1] = UPPER_HEX_DIGITS[0xC | cp];
- return dest;
- } else if (cp <= 0xffff) {
- // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff]
- // Start with "%E-%--%--" and fill in the blanks
- char[] dest = new char[9];
- dest[0] = '%';
- dest[1] = 'E';
- dest[3] = '%';
- dest[6] = '%';
- dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp];
- return dest;
- } else if (cp <= 0x10ffff) {
- char[] dest = new char[12];
- // Four byte UTF-8 characters [cp >= 0xffff && cp <= 0x10ffff]
- // Start with "%F-%--%--%--" and fill in the blanks
- dest[0] = '%';
- dest[1] = 'F';
- dest[3] = '%';
- dest[6] = '%';
- dest[9] = '%';
- dest[11] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp & 0x7];
- return dest;
- } else {
- // If this ever happens it is due to bug in UnicodeEscaper, not bad
- // input.
- throw new IllegalArgumentException("Invalid unicode character value " + cp);
- }
+ /**
+ * Escapes the given Unicode code point in UTF-8.
+ */
+ @Override
+ protected char[] escape(int cp) {
+ // We should never get negative values here but if we do it will throw
+ // an
+ // IndexOutOfBoundsException, so at least it will get spotted.
+ if (cp < safeOctets.length && safeOctets[cp]) {
+ return null;
+ } else if (cp == ' ' && plusForSpace) {
+ return URI_ESCAPED_SPACE;
+ } else if (cp <= 0x7F) {
+ // Single byte UTF-8 characters
+ // Start with "%--" and fill in the blanks
+ char[] dest = new char[3];
+ dest[0] = '%';
+ dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
+ dest[1] = UPPER_HEX_DIGITS[cp >>> 4];
+ return dest;
+ } else if (cp <= 0x7ff) {
+ // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff]
+ // Start with "%--%--" and fill in the blanks
+ char[] dest = new char[6];
+ dest[0] = '%';
+ dest[3] = '%';
+ dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[1] = UPPER_HEX_DIGITS[0xC | cp];
+ return dest;
+ } else if (cp <= 0xffff) {
+ // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff]
+ // Start with "%E-%--%--" and fill in the blanks
+ char[] dest = new char[9];
+ dest[0] = '%';
+ dest[1] = 'E';
+ dest[3] = '%';
+ dest[6] = '%';
+ dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[2] = UPPER_HEX_DIGITS[cp];
+ return dest;
+ } else if (cp <= 0x10ffff) {
+ char[] dest = new char[12];
+ // Four byte UTF-8 characters [cp >= 0xffff && cp <= 0x10ffff]
+ // Start with "%F-%--%--%--" and fill in the blanks
+ dest[0] = '%';
+ dest[1] = 'F';
+ dest[3] = '%';
+ dest[6] = '%';
+ dest[9] = '%';
+ dest[11] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
+ cp >>>= 4;
+ dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
+ cp >>>= 2;
+ dest[2] = UPPER_HEX_DIGITS[cp & 0x7];
+ return dest;
+ } else {
+ // If this ever happens it is due to bug in UnicodeEscaper, not bad
+ // input.
+ throw new IllegalArgumentException("Invalid unicode character value " + cp);
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/UnicodeEscaper.java b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/UnicodeEscaper.java
index 54031850..00230df8 100644
--- a/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/UnicodeEscaper.java
+++ b/src/main/java/org/yaml/snakeyaml/external/com/google/gdata/util/common/base/UnicodeEscaper.java
@@ -1,16 +1,15 @@
-/* Copyright (c) 2008 Google Inc.
+/*
+ * Copyright (c) 2008 Google Inc.
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.external.com.google.gdata.util.common.base;
@@ -18,489 +17,446 @@ package org.yaml.snakeyaml.external.com.google.gdata.util.common.base;
import java.io.IOException;
/**
- * An {@link Escaper} that converts literal text into a format safe for
- * inclusion in a particular context (such as an XML document). Typically (but
- * not always), the inverse process of "unescaping" the text is performed
- * automatically by the relevant parser.
- *
+ * An {@link Escaper} that converts literal text into a format safe for inclusion in a particular
+ * context (such as an XML document). Typically (but not always), the inverse process of
+ * "unescaping" the text is performed automatically by the relevant parser.
+ *
* <p>
- * For example, an XML escaper would convert the literal string
- * {@code "Foo<Bar>"} into {@code "Foo&lt;Bar&gt;"} to prevent {@code "<Bar>"}
- * from being confused with an XML tag. When the resulting XML document is
- * parsed, the parser API will return this text as the original literal string
- * {@code "Foo<Bar>"}.
- *
+ * For example, an XML escaper would convert the literal string {@code "Foo<Bar>"} into
+ * {@code "Foo&lt;Bar&gt;"} to prevent {@code "<Bar>"} from being confused with an XML tag. When the
+ * resulting XML document is parsed, the parser API will return this text as the original literal
+ * string {@code "Foo<Bar>"}.
+ *
* <p>
- * <b>Note:</b> This class is similar to {@link CharEscaper} but with one very
- * important difference. A CharEscaper can only process Java <a
- * href="http://en.wikipedia.org/wiki/UTF-16">UTF16</a> characters in isolation
- * and may not cope when it encounters surrogate pairs. This class facilitates
- * the correct escaping of all Unicode characters.
- *
+ * <b>Note:</b> This class is similar to {@link CharEscaper} but with one very important difference.
+ * A CharEscaper can only process Java <a href="http://en.wikipedia.org/wiki/UTF-16">UTF16</a>
+ * characters in isolation and may not cope when it encounters surrogate pairs. This class
+ * facilitates the correct escaping of all Unicode characters.
+ *
* <p>
- * As there are important reasons, including potential security issues, to
- * handle Unicode correctly if you are considering implementing a new escaper
- * you should favor using UnicodeEscaper wherever possible.
- *
+ * As there are important reasons, including potential security issues, to handle Unicode correctly
+ * if you are considering implementing a new escaper you should favor using UnicodeEscaper wherever
+ * possible.
+ *
* <p>
- * A {@code UnicodeEscaper} instance is required to be stateless, and safe when
- * used concurrently by multiple threads.
- *
+ * A {@code UnicodeEscaper} instance is required to be stateless, and safe when used concurrently by
+ * multiple threads.
+ *
* <p>
- * Several popular escapers are defined as constants in the class
- * {@link CharEscapers}. To create your own escapers extend this class and
- * implement the {@link #escape(int)} method.
- *
- *
+ * Several popular escapers are defined as constants in the class {@link CharEscapers}. To create
+ * your own escapers extend this class and implement the {@link #escape(int)} method.
*/
public abstract class UnicodeEscaper implements Escaper {
- /** The amount of padding (chars) to use when growing the escape buffer. */
- private static final int DEST_PAD = 32;
- /**
- * Returns the escaped form of the given Unicode code point, or {@code null}
- * if this code point does not need to be escaped. When called as part of an
- * escaping operation, the given code point is guaranteed to be in the range
- * {@code 0 <= cp <= Character#MAX_CODE_POINT}.
- *
- * <p>
- * If an empty array is returned, this effectively strips the input
- * character from the resulting text.
- *
- * <p>
- * If the character does not need to be escaped, this method should return
- * {@code null}, rather than an array containing the character
- * representation of the code point. This enables the escaping algorithm to
- * perform more efficiently.
- *
- * <p>
- * If the implementation of this method cannot correctly handle a particular
- * code point then it should either throw an appropriate runtime exception
- * or return a suitable replacement character. It must never silently
- * discard invalid input as this may constitute a security risk.
- *
- * @param cp
- * the Unicode code point to escape if necessary
- * @return the replacement characters, or {@code null} if no escaping was
- * needed
- */
- protected abstract char[] escape(int cp);
+ /**
+ * The amount of padding (chars) to use when growing the escape buffer.
+ */
+ private static final int DEST_PAD = 32;
- /**
- * Scans a sub-sequence of characters from a given {@link CharSequence},
- * returning the index of the next character that requires escaping.
- *
- * <p>
- * <b>Note:</b> When implementing an escaper, it is a good idea to override
- * this method for efficiency. The base class implementation determines
- * successive Unicode code points and invokes {@link #escape(int)} for each
- * of them. If the semantics of your escaper are such that code points in
- * the supplementary range are either all escaped or all unescaped, this
- * method can be implemented more efficiently using
- * {@link CharSequence#charAt(int)}.
- *
- * <p>
- * Note however that if your escaper does not escape characters in the
- * supplementary range, you should either continue to validate the
- * correctness of any surrogate characters encountered or provide a clear
- * warning to users that your escaper does not validate its input.
- *
- * <p>
- * See {@link PercentEscaper} for an example.
- *
- * @param csq
- * a sequence of characters
- * @param start
- * the index of the first character to be scanned
- * @param end
- * the index immediately after the last character to be scanned
- * @throws IllegalArgumentException
- * if the scanned sub-sequence of {@code csq} contains invalid
- * surrogate pairs
- */
- protected int nextEscapeIndex(CharSequence csq, int start, int end) {
- int index = start;
- while (index < end) {
- int cp = codePointAt(csq, index, end);
- if (cp < 0 || escape(cp) != null) {
- break;
- }
- index += Character.isSupplementaryCodePoint(cp) ? 2 : 1;
- }
- return index;
- }
+ /**
+ * Returns the escaped form of the given Unicode code point, or {@code null} if this code point
+ * does not need to be escaped. When called as part of an escaping operation, the given code point
+ * is guaranteed to be in the range {@code 0 <= cp <= Character#MAX_CODE_POINT}.
+ *
+ * <p>
+ * If an empty array is returned, this effectively strips the input character from the resulting
+ * text.
+ *
+ * <p>
+ * If the character does not need to be escaped, this method should return {@code null}, rather
+ * than an array containing the character representation of the code point. This enables the
+ * escaping algorithm to perform more efficiently.
+ *
+ * <p>
+ * If the implementation of this method cannot correctly handle a particular code point then it
+ * should either throw an appropriate runtime exception or return a suitable replacement
+ * character. It must never silently discard invalid input as this may constitute a security risk.
+ *
+ * @param cp the Unicode code point to escape if necessary
+ * @return the replacement characters, or {@code null} if no escaping was needed
+ */
+ protected abstract char[] escape(int cp);
- /**
- * Returns the escaped form of a given literal string.
- *
- * <p>
- * If you are escaping input in arbitrary successive chunks, then it is not
- * generally safe to use this method. If an input string ends with an
- * unmatched high surrogate character, then this method will throw
- * {@link IllegalArgumentException}. You should either ensure your input is
- * valid <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> before
- * calling this method or use an escaped {@link Appendable} (as returned by
- * {@link #escape(Appendable)}) which can cope with arbitrarily split input.
- *
- * <p>
- * <b>Note:</b> When implementing an escaper it is a good idea to override
- * this method for efficiency by inlining the implementation of
- * {@link #nextEscapeIndex(CharSequence, int, int)} directly. Doing this for
- * {@link PercentEscaper} more than doubled the performance for unescaped
- * strings (as measured by {@link CharEscapersBenchmark}).
- *
- * @param string
- * the literal string to be escaped
- * @return the escaped form of {@code string}
- * @throws NullPointerException
- * if {@code string} is null
- * @throws IllegalArgumentException
- * if invalid surrogate characters are encountered
- */
- public String escape(String string) {
- int end = string.length();
- int index = nextEscapeIndex(string, 0, end);
- return index == end ? string : escapeSlow(string, index);
+ /**
+ * Scans a sub-sequence of characters from a given {@link CharSequence}, returning the index of
+ * the next character that requires escaping.
+ *
+ * <p>
+ * <b>Note:</b> When implementing an escaper, it is a good idea to override this method for
+ * efficiency. The base class implementation determines successive Unicode code points and invokes
+ * {@link #escape(int)} for each of them. If the semantics of your escaper are such that code
+ * points in the supplementary range are either all escaped or all unescaped, this method can be
+ * implemented more efficiently using {@link CharSequence#charAt(int)}.
+ *
+ * <p>
+ * Note however that if your escaper does not escape characters in the supplementary range, you
+ * should either continue to validate the correctness of any surrogate characters encountered or
+ * provide a clear warning to users that your escaper does not validate its input.
+ *
+ * <p>
+ * See {@link PercentEscaper} for an example.
+ *
+ * @param csq a sequence of characters
+ * @param start the index of the first character to be scanned
+ * @param end the index immediately after the last character to be scanned
+ * @throws IllegalArgumentException if the scanned sub-sequence of {@code csq} contains invalid
+ * surrogate pairs
+ */
+ protected int nextEscapeIndex(CharSequence csq, int start, int end) {
+ int index = start;
+ while (index < end) {
+ int cp = codePointAt(csq, index, end);
+ if (cp < 0 || escape(cp) != null) {
+ break;
+ }
+ index += Character.isSupplementaryCodePoint(cp) ? 2 : 1;
}
+ return index;
+ }
- /**
- * Returns the escaped form of a given literal string, starting at the given
- * index. This method is called by the {@link #escape(String)} method when
- * it discovers that escaping is required. It is protected to allow
- * subclasses to override the fastpath escaping function to inline their
- * escaping test. See {@link CharEscaperBuilder} for an example usage.
- *
- * <p>
- * This method is not reentrant and may only be invoked by the top level
- * {@link #escape(String)} method.
- *
- * @param s
- * the literal string to be escaped
- * @param index
- * the index to start escaping from
- * @return the escaped form of {@code string}
- * @throws NullPointerException
- * if {@code string} is null
- * @throws IllegalArgumentException
- * if invalid surrogate characters are encountered
- */
- protected final String escapeSlow(String s, int index) {
- int end = s.length();
+ /**
+ * Returns the escaped form of a given literal string.
+ *
+ * <p>
+ * If you are escaping input in arbitrary successive chunks, then it is not generally safe to use
+ * this method. If an input string ends with an unmatched high surrogate character, then this
+ * method will throw {@link IllegalArgumentException}. You should either ensure your input is
+ * valid <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> before calling this method or
+ * use an escaped {@link Appendable} (as returned by {@link #escape(Appendable)}) which can cope
+ * with arbitrarily split input.
+ *
+ * <p>
+ * <b>Note:</b> When implementing an escaper it is a good idea to override this method for
+ * efficiency by inlining the implementation of {@link #nextEscapeIndex(CharSequence, int, int)}
+ * directly. Doing this for {@link PercentEscaper} more than doubled the performance for unescaped
+ * strings (as measured by {@link CharEscapersBenchmark}).
+ *
+ * @param string the literal string to be escaped
+ * @return the escaped form of {@code string}
+ * @throws NullPointerException if {@code string} is null
+ * @throws IllegalArgumentException if invalid surrogate characters are encountered
+ */
+ public String escape(String string) {
+ int end = string.length();
+ int index = nextEscapeIndex(string, 0, end);
+ return index == end ? string : escapeSlow(string, index);
+ }
- // Get a destination buffer and setup some loop variables.
- char[] dest = DEST_TL.get();
- int destIndex = 0;
- int unescapedChunkStart = 0;
+ /**
+ * Returns the escaped form of a given literal string, starting at the given index. This method is
+ * called by the {@link #escape(String)} method when it discovers that escaping is required. It is
+ * protected to allow subclasses to override the fastpath escaping function to inline their
+ * escaping test. See {@link CharEscaperBuilder} for an example usage.
+ *
+ * <p>
+ * This method is not reentrant and may only be invoked by the top level {@link #escape(String)}
+ * method.
+ *
+ * @param s the literal string to be escaped
+ * @param index the index to start escaping from
+ * @return the escaped form of {@code string}
+ * @throws NullPointerException if {@code string} is null
+ * @throws IllegalArgumentException if invalid surrogate characters are encountered
+ */
+ protected final String escapeSlow(String s, int index) {
+ int end = s.length();
- while (index < end) {
- int cp = codePointAt(s, index, end);
- if (cp < 0) {
- throw new IllegalArgumentException("Trailing high surrogate at end of input");
- }
- char[] escaped = escape(cp);
- if (escaped != null) {
- int charsSkipped = index - unescapedChunkStart;
+ // Get a destination buffer and setup some loop variables.
+ char[] dest = DEST_TL.get();
+ int destIndex = 0;
+ int unescapedChunkStart = 0;
- // This is the size needed to add the replacement, not the full
- // size needed by the string. We only regrow when we absolutely
- // must.
- int sizeNeeded = destIndex + charsSkipped + escaped.length;
- if (dest.length < sizeNeeded) {
- int destLength = sizeNeeded + (end - index) + DEST_PAD;
- dest = growBuffer(dest, destIndex, destLength);
- }
- // If we have skipped any characters, we need to copy them now.
- if (charsSkipped > 0) {
- s.getChars(unescapedChunkStart, index, dest, destIndex);
- destIndex += charsSkipped;
- }
- if (escaped.length > 0) {
- System.arraycopy(escaped, 0, dest, destIndex, escaped.length);
- destIndex += escaped.length;
- }
- }
- unescapedChunkStart = index + (Character.isSupplementaryCodePoint(cp) ? 2 : 1);
- index = nextEscapeIndex(s, unescapedChunkStart, end);
- }
+ while (index < end) {
+ int cp = codePointAt(s, index, end);
+ if (cp < 0) {
+ throw new IllegalArgumentException("Trailing high surrogate at end of input");
+ }
+ char[] escaped = escape(cp);
+ if (escaped != null) {
+ int charsSkipped = index - unescapedChunkStart;
- // Process trailing unescaped characters - no need to account for
- // escaped
- // length or padding the allocation.
- int charsSkipped = end - unescapedChunkStart;
+ // This is the size needed to add the replacement, not the full
+ // size needed by the string. We only regrow when we absolutely
+ // must.
+ int sizeNeeded = destIndex + charsSkipped + escaped.length;
+ if (dest.length < sizeNeeded) {
+ int destLength = sizeNeeded + (end - index) + DEST_PAD;
+ dest = growBuffer(dest, destIndex, destLength);
+ }
+ // If we have skipped any characters, we need to copy them now.
if (charsSkipped > 0) {
- int endIndex = destIndex + charsSkipped;
- if (dest.length < endIndex) {
- dest = growBuffer(dest, destIndex, endIndex);
- }
- s.getChars(unescapedChunkStart, end, dest, destIndex);
- destIndex = endIndex;
+ s.getChars(unescapedChunkStart, index, dest, destIndex);
+ destIndex += charsSkipped;
}
- return new String(dest, 0, destIndex);
+ if (escaped.length > 0) {
+ System.arraycopy(escaped, 0, dest, destIndex, escaped.length);
+ destIndex += escaped.length;
+ }
+ }
+ unescapedChunkStart = index + (Character.isSupplementaryCodePoint(cp) ? 2 : 1);
+ index = nextEscapeIndex(s, unescapedChunkStart, end);
}
- /**
- * Returns an {@code Appendable} instance which automatically escapes all
- * text appended to it before passing the resulting text to an underlying
- * {@code Appendable}.
- *
- * <p>
- * Unlike {@link #escape(String)} it is permitted to append arbitrarily
- * split input to this Appendable, including input that is split over a
- * surrogate pair. In this case the pending high surrogate character will
- * not be processed until the corresponding low surrogate is appended. This
- * means that a trailing high surrogate character at the end of the input
- * cannot be detected and will be silently ignored. This is unavoidable
- * since the Appendable interface has no {@code close()} method, and it is
- * impossible to determine when the last characters have been appended.
- *
- * <p>
- * The methods of the returned object will propagate any exceptions thrown
- * by the underlying {@code Appendable}.
- *
- * <p>
- * For well formed <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
- * the escaping behavior is identical to that of {@link #escape(String)} and
- * the following code is equivalent to (but much slower than)
- * {@code escaper.escape(string)}:
- *
- * <pre>
- * {
- * &#064;code
- * StringBuilder sb = new StringBuilder();
- * escaper.escape(sb).append(string);
- * return sb.toString();
- * }
- * </pre>
- *
- * @param out
- * the underlying {@code Appendable} to append escaped output to
- * @return an {@code Appendable} which passes text to {@code out} after
- * escaping it
- * @throws NullPointerException
- * if {@code out} is null
- * @throws IllegalArgumentException
- * if invalid surrogate characters are encountered
- *
- */
- public Appendable escape(final Appendable out) {
- assert out != null;
+ // Process trailing unescaped characters - no need to account for
+ // escaped
+ // length or padding the allocation.
+ int charsSkipped = end - unescapedChunkStart;
+ if (charsSkipped > 0) {
+ int endIndex = destIndex + charsSkipped;
+ if (dest.length < endIndex) {
+ dest = growBuffer(dest, destIndex, endIndex);
+ }
+ s.getChars(unescapedChunkStart, end, dest, destIndex);
+ destIndex = endIndex;
+ }
+ return new String(dest, 0, destIndex);
+ }
- return new Appendable() {
- int pendingHighSurrogate = -1;
- char[] decodedChars = new char[2];
+ /**
+ * Returns an {@code Appendable} instance which automatically escapes all text appended to it
+ * before passing the resulting text to an underlying {@code Appendable}.
+ *
+ * <p>
+ * Unlike {@link #escape(String)} it is permitted to append arbitrarily split input to this
+ * Appendable, including input that is split over a surrogate pair. In this case the pending high
+ * surrogate character will not be processed until the corresponding low surrogate is appended.
+ * This means that a trailing high surrogate character at the end of the input cannot be detected
+ * and will be silently ignored. This is unavoidable since the Appendable interface has no
+ * {@code close()} method, and it is impossible to determine when the last characters have been
+ * appended.
+ *
+ * <p>
+ * The methods of the returned object will propagate any exceptions thrown by the underlying
+ * {@code Appendable}.
+ *
+ * <p>
+ * For well formed <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> the escaping behavior
+ * is identical to that of {@link #escape(String)} and the following code is equivalent to (but
+ * much slower than) {@code escaper.escape(string)}:
+ *
+ * <pre>
+ * {
+ * &#064;code
+ * StringBuilder sb = new StringBuilder();
+ * escaper.escape(sb).append(string);
+ * return sb.toString();
+ * }
+ * </pre>
+ *
+ * @param out the underlying {@code Appendable} to append escaped output to
+ * @return an {@code Appendable} which passes text to {@code out} after escaping it
+ * @throws NullPointerException if {@code out} is null
+ * @throws IllegalArgumentException if invalid surrogate characters are encountered
+ */
+ public Appendable escape(final Appendable out) {
+ assert out != null;
- public Appendable append(CharSequence csq) throws IOException {
- return append(csq, 0, csq.length());
- }
+ return new Appendable() {
+ int pendingHighSurrogate = -1;
+ final char[] decodedChars = new char[2];
- public Appendable append(CharSequence csq, int start, int end) throws IOException {
- int index = start;
- if (index < end) {
- // This is a little subtle: index must never reference the
- // middle of a
- // surrogate pair but unescapedChunkStart can. The first
- // time we enter
- // the loop below it is possible that index !=
- // unescapedChunkStart.
- int unescapedChunkStart = index;
- if (pendingHighSurrogate != -1) {
- // Our last append operation ended halfway through a
- // surrogate pair
- // so we have to do some extra work first.
- char c = csq.charAt(index++);
- if (!Character.isLowSurrogate(c)) {
- throw new IllegalArgumentException(
- "Expected low surrogate character but got " + c);
- }
- char[] escaped = escape(Character.toCodePoint((char) pendingHighSurrogate,
- c));
- if (escaped != null) {
- // Emit the escaped character and adjust
- // unescapedChunkStart to
- // skip the low surrogate we have consumed.
- outputChars(escaped, escaped.length);
- unescapedChunkStart += 1;
- } else {
- // Emit pending high surrogate (unescaped) but do
- // not modify
- // unescapedChunkStart as we must still emit the low
- // surrogate.
- out.append((char) pendingHighSurrogate);
- }
- pendingHighSurrogate = -1;
- }
- while (true) {
- // Find and append the next subsequence of unescaped
- // characters.
- index = nextEscapeIndex(csq, index, end);
- if (index > unescapedChunkStart) {
- out.append(csq, unescapedChunkStart, index);
- }
- if (index == end) {
- break;
- }
- // If we are not finished, calculate the next code
- // point.
- int cp = codePointAt(csq, index, end);
- if (cp < 0) {
- // Our sequence ended half way through a surrogate
- // pair so just
- // record the state and exit.
- pendingHighSurrogate = -cp;
- break;
- }
- // Escape the code point and output the characters.
- char[] escaped = escape(cp);
- if (escaped != null) {
- outputChars(escaped, escaped.length);
- } else {
- // This shouldn't really happen if nextEscapeIndex
- // is correct but
- // we should cope with false positives.
- int len = Character.toChars(cp, decodedChars, 0);
- outputChars(decodedChars, len);
- }
- // Update our index past the escaped character and
- // continue.
- index += (Character.isSupplementaryCodePoint(cp) ? 2 : 1);
- unescapedChunkStart = index;
- }
- }
- return this;
- }
+ public Appendable append(CharSequence csq) throws IOException {
+ return append(csq, 0, csq.length());
+ }
- public Appendable append(char c) throws IOException {
- if (pendingHighSurrogate != -1) {
- // Our last append operation ended halfway through a
- // surrogate pair
- // so we have to do some extra work first.
- if (!Character.isLowSurrogate(c)) {
- throw new IllegalArgumentException(
- "Expected low surrogate character but got '" + c + "' with value "
- + (int) c);
- }
- char[] escaped = escape(Character.toCodePoint((char) pendingHighSurrogate, c));
- if (escaped != null) {
- outputChars(escaped, escaped.length);
- } else {
- out.append((char) pendingHighSurrogate);
- out.append(c);
- }
- pendingHighSurrogate = -1;
- } else if (Character.isHighSurrogate(c)) {
- // This is the start of a (split) surrogate pair.
- pendingHighSurrogate = c;
- } else {
- if (Character.isLowSurrogate(c)) {
- throw new IllegalArgumentException("Unexpected low surrogate character '"
- + c + "' with value " + (int) c);
- }
- // This is a normal (non surrogate) char.
- char[] escaped = escape(c);
- if (escaped != null) {
- outputChars(escaped, escaped.length);
- } else {
- out.append(c);
- }
- }
- return this;
+ public Appendable append(CharSequence csq, int start, int end) throws IOException {
+ int index = start;
+ if (index < end) {
+ // This is a little subtle: index must never reference the
+ // middle of a
+ // surrogate pair but unescapedChunkStart can. The first
+ // time we enter
+ // the loop below it is possible that index !=
+ // unescapedChunkStart.
+ int unescapedChunkStart = index;
+ if (pendingHighSurrogate != -1) {
+ // Our last append operation ended halfway through a
+ // surrogate pair
+ // so we have to do some extra work first.
+ char c = csq.charAt(index++);
+ if (!Character.isLowSurrogate(c)) {
+ throw new IllegalArgumentException("Expected low surrogate character but got " + c);
}
-
- private void outputChars(char[] chars, int len) throws IOException {
- for (int n = 0; n < len; n++) {
- out.append(chars[n]);
- }
+ char[] escaped = escape(Character.toCodePoint((char) pendingHighSurrogate, c));
+ if (escaped != null) {
+ // Emit the escaped character and adjust
+ // unescapedChunkStart to
+ // skip the low surrogate we have consumed.
+ outputChars(escaped, escaped.length);
+ unescapedChunkStart += 1;
+ } else {
+ // Emit pending high surrogate (unescaped) but do
+ // not modify
+ // unescapedChunkStart as we must still emit the low
+ // surrogate.
+ out.append((char) pendingHighSurrogate);
}
- };
- }
-
- /**
- * Returns the Unicode code point of the character at the given index.
- *
- * <p>
- * Unlike {@link Character#codePointAt(CharSequence, int)} or
- * {@link String#codePointAt(int)} this method will never fail silently when
- * encountering an invalid surrogate pair.
- *
- * <p>
- * The behaviour of this method is as follows:
- * <ol>
- * <li>If {@code index >= end}, {@link IndexOutOfBoundsException} is thrown.
- * <li><b>If the character at the specified index is not a surrogate, it is
- * returned.</b>
- * <li>If the first character was a high surrogate value, then an attempt is
- * made to read the next character.
- * <ol>
- * <li><b>If the end of the sequence was reached, the negated value of the
- * trailing high surrogate is returned.</b>
- * <li><b>If the next character was a valid low surrogate, the code point
- * value of the high/low surrogate pair is returned.</b>
- * <li>If the next character was not a low surrogate value, then
- * {@link IllegalArgumentException} is thrown.
- * </ol>
- * <li>If the first character was a low surrogate value,
- * {@link IllegalArgumentException} is thrown.
- * </ol>
- *
- * @param seq
- * the sequence of characters from which to decode the code point
- * @param index
- * the index of the first character to decode
- * @param end
- * the index beyond the last valid character to decode
- * @return the Unicode code point for the given index or the negated value
- * of the trailing high surrogate character at the end of the
- * sequence
- */
- protected static final int codePointAt(CharSequence seq, int index, int end) {
- if (index < end) {
- char c1 = seq.charAt(index++);
- if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) {
- // Fast path (first test is probably all we need to do)
- return c1;
- } else if (c1 <= Character.MAX_HIGH_SURROGATE) {
- // If the high surrogate was the last character, return its
- // inverse
- if (index == end) {
- return -c1;
- }
- // Otherwise look for the low surrogate following it
- char c2 = seq.charAt(index);
- if (Character.isLowSurrogate(c2)) {
- return Character.toCodePoint(c1, c2);
- }
- throw new IllegalArgumentException("Expected low surrogate but got char '" + c2
- + "' with value " + (int) c2 + " at index " + index);
+ pendingHighSurrogate = -1;
+ }
+ while (true) {
+ // Find and append the next subsequence of unescaped
+ // characters.
+ index = nextEscapeIndex(csq, index, end);
+ if (index > unescapedChunkStart) {
+ out.append(csq, unescapedChunkStart, index);
+ }
+ if (index == end) {
+ break;
+ }
+ // If we are not finished, calculate the next code
+ // point.
+ int cp = codePointAt(csq, index, end);
+ if (cp < 0) {
+ // Our sequence ended half way through a surrogate
+ // pair so just
+ // record the state and exit.
+ pendingHighSurrogate = -cp;
+ break;
+ }
+ // Escape the code point and output the characters.
+ char[] escaped = escape(cp);
+ if (escaped != null) {
+ outputChars(escaped, escaped.length);
} else {
- throw new IllegalArgumentException("Unexpected low surrogate character '" + c1
- + "' with value " + (int) c1 + " at index " + (index - 1));
+ // This shouldn't really happen if nextEscapeIndex
+ // is correct but
+ // we should cope with false positives.
+ int len = Character.toChars(cp, decodedChars, 0);
+ outputChars(decodedChars, len);
}
+ // Update our index past the escaped character and
+ // continue.
+ index += (Character.isSupplementaryCodePoint(cp) ? 2 : 1);
+ unescapedChunkStart = index;
+ }
}
- throw new IndexOutOfBoundsException("Index exceeds specified range");
- }
+ return this;
+ }
- /**
- * Helper method to grow the character buffer as needed, this only happens
- * once in a while so it's ok if it's in a method call. If the index passed
- * in is 0 then no copying will be done.
- */
- private static final char[] growBuffer(char[] dest, int index, int size) {
- char[] copy = new char[size];
- if (index > 0) {
- System.arraycopy(dest, 0, copy, 0, index);
+ public Appendable append(char c) throws IOException {
+ if (pendingHighSurrogate != -1) {
+ // Our last append operation ended halfway through a
+ // surrogate pair
+ // so we have to do some extra work first.
+ if (!Character.isLowSurrogate(c)) {
+ throw new IllegalArgumentException(
+ "Expected low surrogate character but got '" + c + "' with value " + (int) c);
+ }
+ char[] escaped = escape(Character.toCodePoint((char) pendingHighSurrogate, c));
+ if (escaped != null) {
+ outputChars(escaped, escaped.length);
+ } else {
+ out.append((char) pendingHighSurrogate);
+ out.append(c);
+ }
+ pendingHighSurrogate = -1;
+ } else if (Character.isHighSurrogate(c)) {
+ // This is the start of a (split) surrogate pair.
+ pendingHighSurrogate = c;
+ } else {
+ if (Character.isLowSurrogate(c)) {
+ throw new IllegalArgumentException(
+ "Unexpected low surrogate character '" + c + "' with value " + (int) c);
+ }
+ // This is a normal (non surrogate) char.
+ char[] escaped = escape(c);
+ if (escaped != null) {
+ outputChars(escaped, escaped.length);
+ } else {
+ out.append(c);
+ }
}
- return copy;
- }
+ return this;
+ }
- /**
- * A thread-local destination buffer to keep us from creating new buffers.
- * The starting size is 1024 characters. If we grow past this we don't put
- * it back in the threadlocal, we just keep going and grow as needed.
- */
- private static final ThreadLocal<char[]> DEST_TL = new ThreadLocal<char[]>() {
- @Override
- protected char[] initialValue() {
- return new char[1024];
+ private void outputChars(char[] chars, int len) throws IOException {
+ for (int n = 0; n < len; n++) {
+ out.append(chars[n]);
}
+ }
};
+ }
+
+ /**
+ * Returns the Unicode code point of the character at the given index.
+ *
+ * <p>
+ * Unlike {@link Character#codePointAt(CharSequence, int)} or {@link String#codePointAt(int)} this
+ * method will never fail silently when encountering an invalid surrogate pair.
+ *
+ * <p>
+ * The behaviour of this method is as follows:
+ * <ol>
+ * <li>If {@code index >= end}, {@link IndexOutOfBoundsException} is thrown.
+ * <li><b>If the character at the specified index is not a surrogate, it is returned.</b>
+ * <li>If the first character was a high surrogate value, then an attempt is made to read the next
+ * character.
+ * <ol>
+ * <li><b>If the end of the sequence was reached, the negated value of the trailing high surrogate
+ * is returned.</b>
+ * <li><b>If the next character was a valid low surrogate, the code point value of the high/low
+ * surrogate pair is returned.</b>
+ * <li>If the next character was not a low surrogate value, then {@link IllegalArgumentException}
+ * is thrown.
+ * </ol>
+ * <li>If the first character was a low surrogate value, {@link IllegalArgumentException} is
+ * thrown.
+ * </ol>
+ *
+ * @param seq the sequence of characters from which to decode the code point
+ * @param index the index of the first character to decode
+ * @param end the index beyond the last valid character to decode
+ * @return the Unicode code point for the given index or the negated value of the trailing high
+ * surrogate character at the end of the sequence
+ */
+ protected static final int codePointAt(CharSequence seq, int index, int end) {
+ if (index < end) {
+ char c1 = seq.charAt(index++);
+ if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) {
+ // Fast path (first test is probably all we need to do)
+ return c1;
+ } else if (c1 <= Character.MAX_HIGH_SURROGATE) {
+ // If the high surrogate was the last character, return its
+ // inverse
+ if (index == end) {
+ return -c1;
+ }
+ // Otherwise look for the low surrogate following it
+ char c2 = seq.charAt(index);
+ if (Character.isLowSurrogate(c2)) {
+ return Character.toCodePoint(c1, c2);
+ }
+ throw new IllegalArgumentException("Expected low surrogate but got char '" + c2
+ + "' with value " + (int) c2 + " at index " + index);
+ } else {
+ throw new IllegalArgumentException("Unexpected low surrogate character '" + c1
+ + "' with value " + (int) c1 + " at index " + (index - 1));
+ }
+ }
+ throw new IndexOutOfBoundsException("Index exceeds specified range");
+ }
+
+ /**
+ * Helper method to grow the character buffer as needed, this only happens once in a while so it's
+ * ok if it's in a method call. If the index passed in is 0 then no copying will be done.
+ */
+ private static final char[] growBuffer(char[] dest, int index, int size) {
+ char[] copy = new char[size];
+ if (index > 0) {
+ System.arraycopy(dest, 0, copy, 0, index);
+ }
+ return copy;
+ }
+
+ /**
+ * A thread-local destination buffer to keep us from creating new buffers. The starting size is
+ * 1024 characters. If we grow past this we don't put it back in the threadlocal, we just keep
+ * going and grow as needed.
+ */
+ private static final ThreadLocal<char[]> DEST_TL = new ThreadLocal<char[]>() {
+ @Override
+ protected char[] initialValue() {
+ return new char[1024];
+ }
+ };
}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/BeanAccess.java b/src/main/java/org/yaml/snakeyaml/introspector/BeanAccess.java
index 696fec73..04454673 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/BeanAccess.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/BeanAccess.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
@@ -19,12 +17,12 @@ package org.yaml.snakeyaml.introspector;
* Control instance variables.
*/
public enum BeanAccess {
- /** use JavaBean properties and public fields */
- DEFAULT,
+ /** use JavaBean properties and public fields */
+ DEFAULT,
- /** use all declared fields (including inherited) */
- FIELD,
+ /** use all declared fields (including inherited) */
+ FIELD,
- /** reserved */
- PROPERTY;
-} \ No newline at end of file
+ /** reserved */
+ PROPERTY
+}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/FieldProperty.java b/src/main/java/org/yaml/snakeyaml/introspector/FieldProperty.java
index 9bc38aac..6d064174 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/FieldProperty.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/FieldProperty.java
@@ -1,52 +1,64 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-
+import java.util.List;
import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.util.ArrayUtils;
/**
* <p>
- * A <code>FieldProperty</code> is a <code>Property</code> which is accessed as
- * a field, without going through accessor methods (setX, getX). The field may
- * have any scope (public, package, protected, private).
+ * A <code>FieldProperty</code> is a <code>Property</code> which is accessed as a field, without
+ * going through accessor methods (setX, getX). The field may have any scope (public, package,
+ * protected, private).
* </p>
*/
public class FieldProperty extends GenericProperty {
- private final Field field;
- public FieldProperty(Field field) {
- super(field.getName(), field.getType(), field.getGenericType());
- this.field = field;
- field.setAccessible(true);
- }
+ private final Field field;
- @Override
- public void set(Object object, Object value) throws Exception {
- field.set(object, value);
- }
+ public FieldProperty(Field field) {
+ super(field.getName(), field.getType(), field.getGenericType());
+ this.field = field;
+ field.setAccessible(true);
+ }
+
+ @Override
+ public void set(Object object, Object value) throws Exception {
+ field.set(object, value);
+ }
- @Override
- public Object get(Object object) {
- try {
- return field.get(object);
- } catch (Exception e) {
- throw new YAMLException("Unable to access field " + field.getName() + " on object "
- + object + " : " + e);
- }
+ @Override
+ public Object get(Object object) {
+ try {
+ return field.get(object);
+ } catch (Exception e) {
+ throw new YAMLException(
+ "Unable to access field " + field.getName() + " on object " + object + " : " + e);
}
-} \ No newline at end of file
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ return ArrayUtils.toUnmodifiableList(field.getAnnotations());
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
+ return field.getAnnotation(annotationType);
+ }
+
+}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/GenericProperty.java b/src/main/java/org/yaml/snakeyaml/introspector/GenericProperty.java
index 30981601..7fff2b5e 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/GenericProperty.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/GenericProperty.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
@@ -20,63 +18,62 @@ import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-abstract public class GenericProperty extends Property {
+public abstract class GenericProperty extends Property {
- private Type genType;
+ private final Type genType;
- public GenericProperty(String name, Class<?> aClass, Type aType) {
- super(name, aClass);
- genType = aType;
- actualClassesChecked = aType == null;
- }
+ public GenericProperty(String name, Class<?> aClass, Type aType) {
+ super(name, aClass);
+ genType = aType;
+ actualClassesChecked = aType == null;
+ }
- private boolean actualClassesChecked;
- private Class<?>[] actualClasses;
+ private boolean actualClassesChecked;
+ private Class<?>[] actualClasses;
- public Class<?>[] getActualTypeArguments() { // should we synchronize here ?
- if (!actualClassesChecked) {
- if (genType instanceof ParameterizedType) {
- ParameterizedType parameterizedType = (ParameterizedType) genType;
- Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
- if (actualTypeArguments.length > 0) {
- actualClasses = new Class<?>[actualTypeArguments.length];
- for (int i = 0; i < actualTypeArguments.length; i++) {
- if (actualTypeArguments[i] instanceof Class<?>) {
- actualClasses[i] = (Class<?>) actualTypeArguments[i];
- } else if (actualTypeArguments[i] instanceof ParameterizedType) {
- actualClasses[i] = (Class<?>) ((ParameterizedType) actualTypeArguments[i])
- .getRawType();
- } else if (actualTypeArguments[i] instanceof GenericArrayType) {
- Type componentType = ((GenericArrayType) actualTypeArguments[i])
- .getGenericComponentType();
- if (componentType instanceof Class<?>) {
- actualClasses[i] = Array.newInstance((Class<?>) componentType, 0)
- .getClass();
- } else {
- actualClasses = null;
- break;
- }
- } else {
- actualClasses = null;
- break;
- }
- }
- }
- } else if (genType instanceof GenericArrayType) {
- Type componentType = ((GenericArrayType) genType).getGenericComponentType();
- if (componentType instanceof Class<?>) {
- actualClasses = new Class<?>[] { (Class<?>) componentType };
- }
- } else if (genType instanceof Class<?>) {// XXX this check is only
- // required for IcedTea6
- Class<?> classType = (Class<?>) genType;
- if (classType.isArray()) {
- actualClasses = new Class<?>[1];
- actualClasses[0] = getType().getComponentType();
- }
+ public Class<?>[] getActualTypeArguments() { // should we synchronize here ?
+ if (!actualClassesChecked) {
+ if (genType instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) genType;
+ Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+ if (actualTypeArguments.length > 0) {
+ actualClasses = new Class<?>[actualTypeArguments.length];
+ for (int i = 0; i < actualTypeArguments.length; i++) {
+ if (actualTypeArguments[i] instanceof Class<?>) {
+ actualClasses[i] = (Class<?>) actualTypeArguments[i];
+ } else if (actualTypeArguments[i] instanceof ParameterizedType) {
+ actualClasses[i] =
+ (Class<?>) ((ParameterizedType) actualTypeArguments[i]).getRawType();
+ } else if (actualTypeArguments[i] instanceof GenericArrayType) {
+ Type componentType =
+ ((GenericArrayType) actualTypeArguments[i]).getGenericComponentType();
+ if (componentType instanceof Class<?>) {
+ actualClasses[i] = Array.newInstance((Class<?>) componentType, 0).getClass();
+ } else {
+ actualClasses = null;
+ break;
+ }
+ } else {
+ actualClasses = null;
+ break;
}
- actualClassesChecked = true;
+ }
+ }
+ } else if (genType instanceof GenericArrayType) {
+ Type componentType = ((GenericArrayType) genType).getGenericComponentType();
+ if (componentType instanceof Class<?>) {
+ actualClasses = new Class<?>[] {(Class<?>) componentType};
+ }
+ } else if (genType instanceof Class<?>) {// XXX this check is only
+ // required for IcedTea6
+ Class<?> classType = (Class<?>) genType;
+ if (classType.isArray()) {
+ actualClasses = new Class<?>[1];
+ actualClasses[0] = getType().getComponentType();
}
- return actualClasses;
+ }
+ actualClassesChecked = true;
}
+ return actualClasses;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/MethodProperty.java b/src/main/java/org/yaml/snakeyaml/introspector/MethodProperty.java
index fc6e00d5..c27fa139 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/MethodProperty.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/MethodProperty.java
@@ -1,71 +1,137 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
import java.beans.PropertyDescriptor;
-
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.util.ArrayUtils;
/**
* <p>
- * A <code>MethodProperty</code> is a <code>Property</code> which is accessed
- * through accessor methods (setX, getX). It is possible to have a
- * <code>MethodProperty</code> which has only setter, only getter, or both. It
- * is not possible to have a <code>MethodProperty</code> which has neither
+ * A <code>MethodProperty</code> is a <code>Property</code> which is accessed through accessor
+ * methods (setX, getX). It is possible to have a <code>MethodProperty</code> which has only setter,
+ * only getter, or both. It is not possible to have a <code>MethodProperty</code> which has neither
* setter nor getter.
* </p>
*/
public class MethodProperty extends GenericProperty {
- private final PropertyDescriptor property;
- private final boolean readable;
- private final boolean writable;
+ private final PropertyDescriptor property;
+ private final boolean readable;
+ private final boolean writable;
+
+ private static Type discoverGenericType(PropertyDescriptor property) {
+ Method readMethod = property.getReadMethod();
+ if (readMethod != null) {
+ return readMethod.getGenericReturnType();
+ }
- public MethodProperty(PropertyDescriptor property) {
- super(property.getName(), property.getPropertyType(),
- property.getReadMethod() == null ? null : property.getReadMethod()
- .getGenericReturnType());
- this.property = property;
- this.readable = property.getReadMethod() != null;
- this.writable = property.getWriteMethod() != null;
+ Method writeMethod = property.getWriteMethod();
+ if (writeMethod != null) {
+ Type[] paramTypes = writeMethod.getGenericParameterTypes();
+ if (paramTypes.length > 0) {
+ return paramTypes[0];
+ }
}
+ /*
+ * This actually may happen if PropertyDescriptor is of type IndexedPropertyDescriptor and it
+ * has only IndexedGetter/Setter. ATM we simply skip type discovery.
+ */
+ return null;
+ }
+
+ public MethodProperty(PropertyDescriptor property) {
+ super(property.getName(), property.getPropertyType(),
+ MethodProperty.discoverGenericType(property));
+ this.property = property;
+ this.readable = property.getReadMethod() != null;
+ this.writable = property.getWriteMethod() != null;
+ }
- @Override
- public void set(Object object, Object value) throws Exception {
- property.getWriteMethod().invoke(object, value);
+ @Override
+ public void set(Object object, Object value) throws Exception {
+ if (!writable) {
+ throw new YAMLException(
+ "No writable property '" + getName() + "' on class: " + object.getClass().getName());
}
+ property.getWriteMethod().invoke(object, value);
+ }
- @Override
- public Object get(Object object) {
- try {
- property.getReadMethod().setAccessible(true);// issue 50
- return property.getReadMethod().invoke(object);
- } catch (Exception e) {
- throw new YAMLException("Unable to find getter for property '" + property.getName()
- + "' on object " + object + ":" + e);
- }
+ @Override
+ public Object get(Object object) {
+ try {
+ property.getReadMethod().setAccessible(true);// issue 50
+ return property.getReadMethod().invoke(object);
+ } catch (Exception e) {
+ throw new YAMLException("Unable to find getter for property '" + property.getName()
+ + "' on object " + object + ":" + e);
}
+ }
- @Override
- public boolean isWritable() {
- return writable;
+ /**
+ * Returns the annotations that are present on read and write methods of this property or empty
+ * {@code List} if there're no annotations.
+ *
+ * @return the annotations that are present on this property or empty {@code List} if there're no
+ * annotations
+ */
+ @Override
+ public List<Annotation> getAnnotations() {
+ List<Annotation> annotations;
+ if (isReadable() && isWritable()) {
+ annotations = ArrayUtils.toUnmodifiableCompositeList(
+ property.getReadMethod().getAnnotations(), property.getWriteMethod().getAnnotations());
+ } else if (isReadable()) {
+ annotations = ArrayUtils.toUnmodifiableList(property.getReadMethod().getAnnotations());
+ } else {
+ annotations = ArrayUtils.toUnmodifiableList(property.getWriteMethod().getAnnotations());
}
+ return annotations;
+ }
- @Override
- public boolean isReadable() {
- return readable;
+ /**
+ * Returns property's annotation for the given type or {@code null} if it's not present. If the
+ * annotation is present on both read and write methods, the annotation on read method takes
+ * precedence.
+ *
+ * @param annotationType the type of the annotation to be returned
+ * @return property's annotation for the given type or {@code null} if it's not present
+ */
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
+ A annotation = null;
+ if (isReadable()) {
+ annotation = property.getReadMethod().getAnnotation(annotationType);
}
-} \ No newline at end of file
+ if (annotation == null && isWritable()) {
+ annotation = property.getWriteMethod().getAnnotation(annotationType);
+ }
+ return annotation;
+ }
+
+ @Override
+ public boolean isWritable() {
+ return writable;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return readable;
+ }
+
+}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/MissingProperty.java b/src/main/java/org/yaml/snakeyaml/introspector/MissingProperty.java
index b16a1c27..97d7233c 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/MissingProperty.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/MissingProperty.java
@@ -1,44 +1,56 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.List;
+
/**
- * A property that does not map to a real property; this is used when {@link
- * PropertyUtils.setSkipMissingProperties(boolean)} is set to true.
+ * A property that does not map to a real property; this is used when
+ * {@link PropertyUtils}.setSkipMissingProperties(boolean) is set to true.
*/
public class MissingProperty extends Property {
- public MissingProperty(String name) {
- super(name, Object.class);
- }
-
- @Override
- public Class<?>[] getActualTypeArguments() {
- return new Class[0];
- }
-
- /**
- * Setter does nothing.
- */
- @Override
- public void set(Object object, Object value) throws Exception {
- }
-
- @Override
- public Object get(Object object) {
- return object;
- }
+ public MissingProperty(String name) {
+ super(name, Object.class);
+ }
+
+ @Override
+ public Class<?>[] getActualTypeArguments() {
+ return new Class[0];
+ }
+
+ /**
+ * Setter does nothing.
+ */
+ @Override
+ public void set(Object object, Object value) throws Exception {}
+
+ @Override
+ public Object get(Object object) {
+ return object;
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
+ return null;
+ }
+
}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/Property.java b/src/main/java/org/yaml/snakeyaml/introspector/Property.java
index 286ca0e3..a65d8505 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/Property.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/Property.java
@@ -1,85 +1,104 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
+import java.lang.annotation.Annotation;
+import java.util.List;
+
/**
* <p>
- * A <code>Property</code> represents a single member variable of a class,
- * possibly including its accessor methods (getX, setX). The name stored in this
- * class is the actual name of the property as given for the class, not an
- * alias.
+ * A <code>Property</code> represents a single member variable of a class, possibly including its
+ * accessor methods (getX, setX). The name stored in this class is the actual name of the property
+ * as given for the class, not an alias.
* </p>
- *
+ *
* <p>
- * Objects of this class have a total ordering which defaults to ordering based
- * on the name of the property.
+ * Objects of this class have a total ordering which defaults to ordering based on the name of the
+ * property.
* </p>
*/
public abstract class Property implements Comparable<Property> {
- private final String name;
- private final Class<?> type;
+ private final String name;
+ private final Class<?> type;
- public Property(String name, Class<?> type) {
- this.name = name;
- this.type = type;
- }
+ public Property(String name, Class<?> type) {
+ this.name = name;
+ this.type = type;
+ }
- public Class<?> getType() {
- return type;
- }
+ public Class<?> getType() {
+ return type;
+ }
- abstract public Class<?>[] getActualTypeArguments();
+ public abstract Class<?>[] getActualTypeArguments();
- public String getName() {
- return name;
- }
+ public String getName() {
+ return name;
+ }
- @Override
- public String toString() {
- return getName() + " of " + getType();
- }
+ @Override
+ public String toString() {
+ return getName() + " of " + getType();
+ }
- public int compareTo(Property o) {
- return name.compareTo(o.name);
- }
+ public int compareTo(Property o) {
+ return getName().compareTo(o.getName());
+ }
- public boolean isWritable() {
- return true;
- }
+ public boolean isWritable() {
+ return true;
+ }
- public boolean isReadable() {
- return true;
- }
+ public boolean isReadable() {
+ return true;
+ }
- abstract public void set(Object object, Object value) throws Exception;
+ public abstract void set(Object object, Object value) throws Exception;
- abstract public Object get(Object object);
+ public abstract Object get(Object object);
- @Override
- public int hashCode() {
- return name.hashCode() + type.hashCode();
- }
+ /**
+ * Returns the annotations that are present on this property or empty {@code List} if there're no
+ * annotations.
+ *
+ * @return the annotations that are present on this property or empty {@code List} if there're no
+ * annotations
+ */
+ public abstract List<Annotation> getAnnotations();
+
+ /**
+ * Returns property's annotation for the given type or {@code null} if it's not present.
+ *
+ * @param annotationType the type of the annotation to be returned
+ * @param <A> class of the annotation
+ *
+ * @return property's annotation for the given type or {@code null} if it's not present
+ */
+ public abstract <A extends Annotation> A getAnnotation(Class<A> annotationType);
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode() + getType().hashCode();
+ }
- @Override
- public boolean equals(Object other) {
- if (other instanceof Property) {
- Property p = (Property) other;
- return name.equals(p.getName()) && type.equals(p.getType());
- }
- return false;
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Property) {
+ Property p = (Property) other;
+ return getName().equals(p.getName()) && getType().equals(p.getType());
}
-} \ No newline at end of file
+ return false;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/PropertySubstitute.java b/src/main/java/org/yaml/snakeyaml/introspector/PropertySubstitute.java
new file mode 100644
index 00000000..162df62a
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/introspector/PropertySubstitute.java
@@ -0,0 +1,253 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.introspector;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.yaml.snakeyaml.error.YAMLException;
+
+// TODO: decide priorities for get/set Read/Field/Delegate Write/Field/Delegate - is FIELD on the
+// correct place ?
+public class PropertySubstitute extends Property {
+
+ private static final Logger log =
+ Logger.getLogger(PropertySubstitute.class.getPackage().getName());
+
+ protected Class<?> targetType;
+ private final String readMethod;
+ private final String writeMethod;
+ private transient Method read;
+ private transient Method write;
+ private Field field;
+ protected Class<?>[] parameters;
+ private Property delegate;
+ private boolean filler;
+
+ public PropertySubstitute(String name, Class<?> type, String readMethod, String writeMethod,
+ Class<?>... params) {
+ super(name, type);
+ this.readMethod = readMethod;
+ this.writeMethod = writeMethod;
+ setActualTypeArguments(params);
+ this.filler = false;
+ }
+
+ public PropertySubstitute(String name, Class<?> type, Class<?>... params) {
+ this(name, type, null, null, params);
+ }
+
+ @Override
+ public Class<?>[] getActualTypeArguments() {
+ if (parameters == null && delegate != null) {
+ return delegate.getActualTypeArguments();
+ }
+ return parameters;
+ }
+
+ public void setActualTypeArguments(Class<?>... args) {
+ if (args != null && args.length > 0) {
+ parameters = args;
+ } else {
+ parameters = null;
+ }
+ }
+
+ @Override
+ public void set(Object object, Object value) throws Exception {
+ if (write != null) {
+ if (!filler) {
+ write.invoke(object, value);
+ } else if (value != null) {
+ if (value instanceof Collection<?>) {
+ Collection<?> collection = (Collection<?>) value;
+ for (Object val : collection) {
+ write.invoke(object, val);
+ }
+ } else if (value instanceof Map<?, ?>) {
+ Map<?, ?> map = (Map<?, ?>) value;
+ for (Entry<?, ?> entry : map.entrySet()) {
+ write.invoke(object, entry.getKey(), entry.getValue());
+ }
+ } else if (value.getClass().isArray()) {
+ // TODO: maybe arrays need 2 fillers like SET(index, value) add ADD(value)
+ int len = Array.getLength(value);
+ for (int i = 0; i < len; i++) {
+ write.invoke(object, Array.get(value, i));
+ }
+ }
+ }
+ } else if (field != null) {
+ field.set(object, value);
+ } else if (delegate != null) {
+ delegate.set(object, value);
+ } else {
+ log.warning("No setter/delegate for '" + getName() + "' on object " + object);
+ }
+ // TODO: maybe throw YAMLException here
+ }
+
+ @Override
+ public Object get(Object object) {
+ try {
+ if (read != null) {
+ return read.invoke(object);
+ } else if (field != null) {
+ return field.get(object);
+ }
+ } catch (Exception e) {
+ throw new YAMLException(
+ "Unable to find getter for property '" + getName() + "' on object " + object + ":" + e);
+ }
+
+ if (delegate != null) {
+ return delegate.get(object);
+ }
+ throw new YAMLException(
+ "No getter or delegate for property '" + getName() + "' on object " + object);
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ Annotation[] annotations = null;
+ if (read != null) {
+ annotations = read.getAnnotations();
+ } else if (field != null) {
+ annotations = field.getAnnotations();
+ }
+ return annotations != null ? Arrays.asList(annotations) : delegate.getAnnotations();
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
+ A annotation;
+ if (read != null) {
+ annotation = read.getAnnotation(annotationType);
+ } else if (field != null) {
+ annotation = field.getAnnotation(annotationType);
+ } else {
+ annotation = delegate.getAnnotation(annotationType);
+ }
+ return annotation;
+ }
+
+ public void setTargetType(Class<?> targetType) {
+ if (this.targetType != targetType) {
+ this.targetType = targetType;
+
+ final String name = getName();
+ for (Class<?> c = targetType; c != null; c = c.getSuperclass()) {
+ for (Field f : c.getDeclaredFields()) {
+ if (f.getName().equals(name)) {
+ int modifiers = f.getModifiers();
+ if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
+ f.setAccessible(true);
+ field = f;
+ }
+ break;
+ }
+ }
+ }
+ if (field == null && log.isLoggable(Level.FINE)) {
+ log.fine(String.format("Failed to find field for %s.%s", targetType.getName(), getName()));
+ }
+
+ // Retrieve needed info
+ if (readMethod != null) {
+ read = discoverMethod(targetType, readMethod);
+ }
+ if (writeMethod != null) {
+ filler = false;
+ write = discoverMethod(targetType, writeMethod, getType());
+ if (write == null && parameters != null) {
+ filler = true;
+ write = discoverMethod(targetType, writeMethod, parameters);
+ }
+ }
+ }
+ }
+
+ private Method discoverMethod(Class<?> type, String name, Class<?>... params) {
+ for (Class<?> c = type; c != null; c = c.getSuperclass()) {
+ for (Method method : c.getDeclaredMethods()) {
+ if (name.equals(method.getName())) {
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length != params.length) {
+ continue;
+ }
+ boolean found = true;
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (!parameterTypes[i].isAssignableFrom(params[i])) {
+ found = false;
+ }
+ }
+ if (found) {
+ method.setAccessible(true);
+ return method;
+ }
+ }
+ }
+ }
+ if (log.isLoggable(Level.FINE)) {
+ log.fine(String.format("Failed to find [%s(%d args)] for %s.%s", name, params.length,
+ targetType.getName(), getName()));
+ }
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ final String n = super.getName();
+ if (n != null) {
+ return n;
+ }
+ return delegate != null ? delegate.getName() : null;
+ }
+
+ @Override
+ public Class<?> getType() {
+ final Class<?> t = super.getType();
+ if (t != null) {
+ return t;
+ }
+ return delegate != null ? delegate.getType() : null;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return (read != null) || (field != null) || (delegate != null && delegate.isReadable());
+ }
+
+ @Override
+ public boolean isWritable() {
+ return (write != null) || (field != null) || (delegate != null && delegate.isWritable());
+ }
+
+ public void setDelegate(Property delegate) {
+ this.delegate = delegate;
+ if (writeMethod != null && write == null && !filler) {
+ filler = true;
+ write = discoverMethod(targetType, writeMethod, getActualTypeArguments());
+ }
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java b/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
index 92519d90..8afc5761 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
@@ -1,20 +1,19 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.introspector;
+import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
@@ -27,140 +26,174 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-
import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.util.PlatformFeatureDetector;
public class PropertyUtils {
- private final Map<Class<?>, Map<String, Property>> propertiesCache = new HashMap<Class<?>, Map<String, Property>>();
- private final Map<Class<?>, Set<Property>> readableProperties = new HashMap<Class<?>, Set<Property>>();
- private BeanAccess beanAccess = BeanAccess.DEFAULT;
- private boolean allowReadOnlyProperties = false;
- private boolean skipMissingProperties = false;
+ private final Map<Class<?>, Map<String, Property>> propertiesCache =
+ new HashMap<Class<?>, Map<String, Property>>();
+ private final Map<Class<?>, Set<Property>> readableProperties =
+ new HashMap<Class<?>, Set<Property>>();
+ private BeanAccess beanAccess = BeanAccess.DEFAULT;
+ private boolean allowReadOnlyProperties = false;
+ private boolean skipMissingProperties = false;
- protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess)
- throws IntrospectionException {
- if (propertiesCache.containsKey(type)) {
- return propertiesCache.get(type);
- }
+ private final PlatformFeatureDetector platformFeatureDetector;
- Map<String, Property> properties = new LinkedHashMap<String, Property>();
- boolean inaccessableFieldsExist = false;
- switch (bAccess) {
- case FIELD:
- for (Class<?> c = type; c != null; c = c.getSuperclass()) {
- for (Field field : c.getDeclaredFields()) {
- int modifiers = field.getModifiers();
- if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)
- && !properties.containsKey(field.getName())) {
- properties.put(field.getName(), new FieldProperty(field));
- }
- }
- }
- break;
- default:
- // add JavaBean properties
- for (PropertyDescriptor property : Introspector.getBeanInfo(type)
- .getPropertyDescriptors()) {
- Method readMethod = property.getReadMethod();
- if (readMethod == null || !readMethod.getName().equals("getClass")) {
- properties.put(property.getName(), new MethodProperty(property));
- }
- }
+ public PropertyUtils() {
+ this(new PlatformFeatureDetector());
+ }
- // add public fields
- for (Class<?> c = type; c != null; c = c.getSuperclass()) {
- for (Field field : c.getDeclaredFields()) {
- int modifiers = field.getModifiers();
- if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
- if (Modifier.isPublic(modifiers)) {
- properties.put(field.getName(), new FieldProperty(field));
- } else {
- inaccessableFieldsExist = true;
- }
- }
- }
- }
- break;
- }
- if (properties.isEmpty() && inaccessableFieldsExist) {
- throw new YAMLException("No JavaBean properties found in " + type.getName());
- }
- propertiesCache.put(type, properties);
- return properties;
+ PropertyUtils(PlatformFeatureDetector platformFeatureDetector) {
+ this.platformFeatureDetector = platformFeatureDetector;
+
+ /*
+ * Android lacks much of java.beans (including the Introspector class, used here), because
+ * java.beans classes tend to rely on java.awt, which isn't supported in the Android SDK. That
+ * means we have to fall back on FIELD access only when SnakeYAML is running on the Android
+ * Runtime.
+ */
+ if (platformFeatureDetector.isRunningOnAndroid()) {
+ beanAccess = BeanAccess.FIELD;
}
+ }
- public Set<Property> getProperties(Class<? extends Object> type) throws IntrospectionException {
- return getProperties(type, beanAccess);
+ protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) {
+ if (propertiesCache.containsKey(type)) {
+ return propertiesCache.get(type);
}
- public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess)
- throws IntrospectionException {
- if (readableProperties.containsKey(type)) {
- return readableProperties.get(type);
+ Map<String, Property> properties = new LinkedHashMap<String, Property>();
+ boolean inaccessableFieldsExist = false;
+ if (bAccess == BeanAccess.FIELD) {
+ for (Class<?> c = type; c != null; c = c.getSuperclass()) {
+ for (Field field : c.getDeclaredFields()) {
+ int modifiers = field.getModifiers();
+ if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)
+ && !properties.containsKey(field.getName())) {
+ properties.put(field.getName(), new FieldProperty(field));
+ }
}
- Set<Property> properties = createPropertySet(type, bAccess);
- readableProperties.put(type, properties);
- return properties;
- }
+ }
+ } else {// add JavaBean properties
+ try {
+ for (PropertyDescriptor property : Introspector.getBeanInfo(type)
+ .getPropertyDescriptors()) {
+ Method readMethod = property.getReadMethod();
+ if ((readMethod == null || !readMethod.getName().equals("getClass"))
+ && !isTransient(property)) {
+ properties.put(property.getName(), new MethodProperty(property));
+ }
+ }
+ } catch (IntrospectionException e) {
+ throw new YAMLException(e);
+ }
- protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)
- throws IntrospectionException {
- Set<Property> properties = new TreeSet<Property>();
- Collection<Property> props = getPropertiesMap(type, bAccess).values();
- for (Property property : props) {
- if (property.isReadable() && (allowReadOnlyProperties || property.isWritable())) {
- properties.add(property);
+ // add public fields
+ for (Class<?> c = type; c != null; c = c.getSuperclass()) {
+ for (Field field : c.getDeclaredFields()) {
+ int modifiers = field.getModifiers();
+ if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
+ if (Modifier.isPublic(modifiers)) {
+ properties.put(field.getName(), new FieldProperty(field));
+ } else {
+ inaccessableFieldsExist = true;
}
+ }
}
- return properties;
+ }
+ }
+ if (properties.isEmpty() && inaccessableFieldsExist) {
+ throw new YAMLException("No JavaBean properties found in " + type.getName());
}
+ propertiesCache.put(type, properties);
+ return properties;
+ }
+
+ private static final String TRANSIENT = "transient";
+
+ private boolean isTransient(FeatureDescriptor fd) {
+ return Boolean.TRUE.equals(fd.getValue(TRANSIENT));
+ }
+
+ public Set<Property> getProperties(Class<? extends Object> type) {
+ return getProperties(type, beanAccess);
+ }
- public Property getProperty(Class<? extends Object> type, String name)
- throws IntrospectionException {
- return getProperty(type, name, beanAccess);
+ public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess) {
+ if (readableProperties.containsKey(type)) {
+ return readableProperties.get(type);
}
+ Set<Property> properties = createPropertySet(type, bAccess);
+ readableProperties.put(type, properties);
+ return properties;
+ }
- public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess)
- throws IntrospectionException {
- Map<String, Property> properties = getPropertiesMap(type, bAccess);
- Property property = properties.get(name);
- if (property == null && skipMissingProperties) {
- property = new MissingProperty(name);
- }
- if (property == null || !property.isWritable()) {
- throw new YAMLException("Unable to find property '" + name + "' on class: "
- + type.getName());
- }
- return property;
+ protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess) {
+ Set<Property> properties = new TreeSet<Property>();
+ Collection<Property> props = getPropertiesMap(type, bAccess).values();
+ for (Property property : props) {
+ if (property.isReadable() && (allowReadOnlyProperties || property.isWritable())) {
+ properties.add(property);
+ }
}
+ return properties;
+ }
- public void setBeanAccess(BeanAccess beanAccess) {
- if (this.beanAccess != beanAccess) {
- this.beanAccess = beanAccess;
- propertiesCache.clear();
- readableProperties.clear();
- }
+ public Property getProperty(Class<? extends Object> type, String name) {
+ return getProperty(type, name, beanAccess);
+ }
+
+ public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess) {
+ Map<String, Property> properties = getPropertiesMap(type, bAccess);
+ Property property = properties.get(name);
+ if (property == null && skipMissingProperties) {
+ property = new MissingProperty(name);
}
+ if (property == null) {
+ throw new YAMLException("Unable to find property '" + name + "' on class: " + type.getName());
+ }
+ return property;
+ }
- public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) {
- if (this.allowReadOnlyProperties != allowReadOnlyProperties) {
- this.allowReadOnlyProperties = allowReadOnlyProperties;
- readableProperties.clear();
- }
+ public void setBeanAccess(BeanAccess beanAccess) {
+ if (platformFeatureDetector.isRunningOnAndroid() && beanAccess != BeanAccess.FIELD) {
+ throw new IllegalArgumentException("JVM is Android - only BeanAccess.FIELD is available");
}
- /**
- * Skip properties that are missing during deserialization of YAML to a Java
- * object. The default is false.
- *
- * @param skipMissingProperties
- * true if missing properties should be skipped, false otherwise.
- */
- public void setSkipMissingProperties(boolean skipMissingProperties) {
- if (this.skipMissingProperties != skipMissingProperties) {
- this.skipMissingProperties = skipMissingProperties;
- readableProperties.clear();
- }
+ if (this.beanAccess != beanAccess) {
+ this.beanAccess = beanAccess;
+ propertiesCache.clear();
+ readableProperties.clear();
+ }
+ }
+
+ public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) {
+ if (this.allowReadOnlyProperties != allowReadOnlyProperties) {
+ this.allowReadOnlyProperties = allowReadOnlyProperties;
+ readableProperties.clear();
}
+ }
+
+ public boolean isAllowReadOnlyProperties() {
+ return allowReadOnlyProperties;
+ }
+
+ /**
+ * Skip properties that are missing during deserialization of YAML to a Java object. The default
+ * is false.
+ *
+ * @param skipMissingProperties true if missing properties should be skipped, false otherwise.
+ */
+ public void setSkipMissingProperties(boolean skipMissingProperties) {
+ if (this.skipMissingProperties != skipMissingProperties) {
+ this.skipMissingProperties = skipMissingProperties;
+ readableProperties.clear();
+ }
+ }
+
+ public boolean isSkipMissingProperties() {
+ return skipMissingProperties;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/AnchorNode.java b/src/main/java/org/yaml/snakeyaml/nodes/AnchorNode.java
index 8a901317..52834120 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/AnchorNode.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/AnchorNode.java
@@ -1,35 +1,36 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
+/**
+ * This class is only used during representation (dumping)
+ */
public class AnchorNode extends Node {
- private Node realNode;
+ private final Node realNode;
- public AnchorNode(Node realNode) {
- super(realNode.getTag(), realNode.getStartMark(), realNode.getEndMark());
- this.realNode = realNode;
- }
+ public AnchorNode(Node realNode) {
+ super(realNode.getTag(), realNode.getStartMark(), realNode.getEndMark());
+ this.realNode = realNode;
+ }
- @Override
- public NodeId getNodeId() {
- return NodeId.anchor;
- }
+ @Override
+ public NodeId getNodeId() {
+ return NodeId.anchor;
+ }
- public Node getRealNode() {
- return realNode;
- }
+ public Node getRealNode() {
+ return realNode;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/CollectionNode.java b/src/main/java/org/yaml/snakeyaml/nodes/CollectionNode.java
index aa443686..09ad80f5 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/CollectionNode.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/CollectionNode.java
@@ -1,49 +1,83 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
+import java.util.List;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
- * Base class for the two collection types {@link MappingNode mapping} and
- * {@link SequenceNode collection}.
+ * Base class for the two collection types {@link MappingNode mapping} and {@link SequenceNode
+ * collection}.
*/
-public abstract class CollectionNode extends Node {
- private Boolean flowStyle;
+public abstract class CollectionNode<T> extends Node {
- public CollectionNode(Tag tag, Mark startMark, Mark endMark, Boolean flowStyle) {
- super(tag, startMark, endMark);
- this.flowStyle = flowStyle;
- }
+ private DumperOptions.FlowStyle flowStyle;
- /**
- * Serialization style of this collection.
- *
- * @return <code>true</code> for flow style, <code>false</code> for block
- * style.
- */
- public Boolean getFlowStyle() {
- return flowStyle;
- }
+ public CollectionNode(Tag tag, Mark startMark, Mark endMark, DumperOptions.FlowStyle flowStyle) {
+ super(tag, startMark, endMark);
+ setFlowStyle(flowStyle);
+ }
- public void setFlowStyle(Boolean flowStyle) {
- this.flowStyle = flowStyle;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based constructor.
+ * Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link CollectionNode#CollectionNode(Tag, Mark, Mark,
+ * org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public CollectionNode(Tag tag, Mark startMark, Mark endMark, Boolean flowStyle) {
+ this(tag, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
+
+ /**
+ * Returns the elements in this sequence.
+ *
+ * @return Nodes in the specified order.
+ */
+ public abstract List<T> getValue();
- public void setEndMark(Mark endMark) {
- this.endMark = endMark;
+ /**
+ * Serialization style of this collection.
+ *
+ * @return <code>true</code> for flow style, <code>false</code> for block style.
+ */
+ public DumperOptions.FlowStyle getFlowStyle() {
+ return flowStyle;
+ }
+
+ public void setFlowStyle(DumperOptions.FlowStyle flowStyle) {
+ if (flowStyle == null) {
+ throw new NullPointerException("Flow style must be provided.");
}
+ this.flowStyle = flowStyle;
+ }
+
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based method.
+ * Restored in v1.26 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.26. Use {@link
+ * CollectionNode#setFlowStyle(org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public void setFlowStyle(Boolean flowStyle) {
+ setFlowStyle(DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
+
+ public void setEndMark(Mark endMark) {
+ this.endMark = endMark;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/MappingNode.java b/src/main/java/org/yaml/snakeyaml/nodes/MappingNode.java
index ed3dcff5..f0f7a84d 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/MappingNode.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/MappingNode.java
@@ -1,22 +1,20 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
import java.util.List;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
@@ -25,87 +23,113 @@ import org.yaml.snakeyaml.error.Mark;
* A map is a collection of unsorted key-value pairs.
* </p>
*/
-public class MappingNode extends CollectionNode {
- private List<NodeTuple> value;
- private boolean merged = false;
-
- public MappingNode(Tag tag, boolean resolved, List<NodeTuple> value, Mark startMark,
- Mark endMark, Boolean flowStyle) {
- super(tag, startMark, endMark, flowStyle);
- if (value == null) {
- throw new NullPointerException("value in a Node is required.");
- }
- this.value = value;
- this.resolved = resolved;
- }
+public class MappingNode extends CollectionNode<NodeTuple> {
- public MappingNode(Tag tag, List<NodeTuple> value, Boolean flowStyle) {
- this(tag, true, value, null, null, flowStyle);
- }
+ private List<NodeTuple> value;
+ private boolean merged = false;
- @Override
- public NodeId getNodeId() {
- return NodeId.mapping;
+ public MappingNode(Tag tag, boolean resolved, List<NodeTuple> value, Mark startMark, Mark endMark,
+ DumperOptions.FlowStyle flowStyle) {
+ super(tag, startMark, endMark, flowStyle);
+ if (value == null) {
+ throw new NullPointerException("value in a Node is required.");
}
+ this.value = value;
+ this.resolved = resolved;
+ }
- /**
- * Returns the entries of this map.
- *
- * @return List of entries.
- */
- public List<NodeTuple> getValue() {
- return value;
- }
+ public MappingNode(Tag tag, List<NodeTuple> value, DumperOptions.FlowStyle flowStyle) {
+ this(tag, true, value, null, null, flowStyle);
+ }
- public void setValue(List<NodeTuple> merge) {
- value = merge;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based constructor.
+ * Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link MappingNode#MappingNode(Tag, boolean, List,
+ * Mark, Mark, org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public MappingNode(Tag tag, boolean resolved, List<NodeTuple> value, Mark startMark, Mark endMark,
+ Boolean flowStyle) {
+ this(tag, resolved, value, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(flowStyle));
+ }
- public void setOnlyKeyType(Class<? extends Object> keyType) {
- for (NodeTuple nodes : value) {
- nodes.getKeyNode().setType(keyType);
- }
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.FlowStyle}-based constructor.
+ * Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link MappingNode#MappingNode(Tag, List,
+ * org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public MappingNode(Tag tag, List<NodeTuple> value, Boolean flowStyle) {
+ this(tag, value, DumperOptions.FlowStyle.fromBoolean(flowStyle));
- public void setTypes(Class<? extends Object> keyType, Class<? extends Object> valueType) {
- for (NodeTuple nodes : value) {
- nodes.getValueNode().setType(valueType);
- nodes.getKeyNode().setType(keyType);
- }
- }
+ }
+
+ @Override
+ public NodeId getNodeId() {
+ return NodeId.mapping;
+ }
+
+ /**
+ * Returns the entries of this map.
+ *
+ * @return List of entries.
+ */
+ public List<NodeTuple> getValue() {
+ return value;
+ }
- @Override
- public String toString() {
- String values;
- StringBuilder buf = new StringBuilder();
- for (NodeTuple node : getValue()) {
- buf.append("{ key=");
- buf.append(node.getKeyNode());
- buf.append("; value=");
- if (node.getValueNode() instanceof CollectionNode) {
- // to avoid overflow in case of recursive structures
- buf.append(System.identityHashCode(node.getValueNode()));
- } else {
- buf.append(node.toString());
- }
- buf.append(" }");
- }
- values = buf.toString();
- return "<" + this.getClass().getName() + " (tag=" + getTag() + ", values=" + values + ")>";
+ public void setValue(List<NodeTuple> mergedValue) {
+ value = mergedValue;
+ }
+
+ public void setOnlyKeyType(Class<? extends Object> keyType) {
+ for (NodeTuple nodes : value) {
+ nodes.getKeyNode().setType(keyType);
}
+ }
- /**
- * @param merged
- * - true if map contains merge node
- */
- public void setMerged(boolean merged) {
- this.merged = merged;
+ public void setTypes(Class<? extends Object> keyType, Class<? extends Object> valueType) {
+ for (NodeTuple nodes : value) {
+ nodes.getValueNode().setType(valueType);
+ nodes.getKeyNode().setType(keyType);
}
+ }
- /**
- * @return true if map contains merge node
- */
- public boolean isMerged() {
- return merged;
+ @Override
+ public String toString() {
+ String values;
+ StringBuilder buf = new StringBuilder();
+ for (NodeTuple node : getValue()) {
+ buf.append("{ key=");
+ buf.append(node.getKeyNode());
+ buf.append("; value=");
+ if (node.getValueNode() instanceof CollectionNode) {
+ // to avoid overflow in case of recursive structures
+ buf.append(System.identityHashCode(node.getValueNode()));
+ } else {
+ buf.append(node);
+ }
+ buf.append(" }");
}
+ values = buf.toString();
+ return "<" + this.getClass().getName() + " (tag=" + getTag() + ", values=" + values + ")>";
+ }
+
+ /**
+ * @param merged - true if map contains merge node
+ */
+ public void setMerged(boolean merged) {
+ this.merged = merged;
+ }
+
+ /**
+ * @return true if map contains merge node
+ */
+ public boolean isMerged() {
+ return merged;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/Node.java b/src/main/java/org/yaml/snakeyaml/nodes/Node.java
index 1037e41a..45b9b9b8 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/Node.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/Node.java
@@ -1,166 +1,225 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
+import java.util.List;
+import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.error.Mark;
/**
* Base class for all nodes.
* <p>
- * The nodes form the node-graph described in the <a
- * href="http://yaml.org/spec/1.1/">YAML Specification</a>.
+ * The nodes form the node-graph described in the <a href="http://yaml.org/spec/1.1/">YAML
+ * Specification</a>.
* </p>
* <p>
* While loading, the node graph is usually created by the
- * {@link org.yaml.snakeyaml.composer.Composer}, and later transformed into
- * application specific Java classes by the classes from the
- * {@link org.yaml.snakeyaml.constructor} package.
+ * {@link org.yaml.snakeyaml.composer.Composer}, and later transformed into application specific
+ * Java classes by the classes from the {@link org.yaml.snakeyaml.constructor} package.
* </p>
*/
public abstract class Node {
- private Tag tag;
- private Mark startMark;
- protected Mark endMark;
- private Class<? extends Object> type;
- private boolean twoStepsConstruction;
- /**
- * true when the tag is assigned by the resolver
- */
- protected boolean resolved;
- protected Boolean useClassConstructor;
-
- public Node(Tag tag, Mark startMark, Mark endMark) {
- setTag(tag);
- this.startMark = startMark;
- this.endMark = endMark;
- this.type = Object.class;
- this.twoStepsConstruction = false;
- this.resolved = true;
- this.useClassConstructor = null;
- }
-
- /**
- * Tag of this node.
- * <p>
- * Every node has a tag assigned. The tag is either local or global.
- *
- * @return Tag of this node.
- */
- public Tag getTag() {
- return this.tag;
- }
-
- public Mark getEndMark() {
- return endMark;
- }
-
- /**
- * For error reporting.
- *
- * @see "class variable 'id' in PyYAML"
- * @return scalar, sequence, mapping
- */
- public abstract NodeId getNodeId();
-
- public Mark getStartMark() {
- return startMark;
- }
-
- public void setTag(Tag tag) {
- if (tag == null) {
- throw new NullPointerException("tag in a Node is required.");
- }
- this.tag = tag;
- }
-
- /**
- * Two Nodes are never equal.
- */
- @Override
- public final boolean equals(Object obj) {
- return super.equals(obj);
- }
- public Class<? extends Object> getType() {
- return type;
+ private Tag tag;
+ private final Mark startMark;
+ protected Mark endMark;
+ private Class<? extends Object> type;
+ private boolean twoStepsConstruction;
+ private String anchor;
+ private List<CommentLine> inLineComments;
+ private List<CommentLine> blockComments;
+ // End Comments are only on the last node in a document
+ private List<CommentLine> endComments;
+
+ /**
+ * true when the tag is assigned by the resolver
+ */
+ protected boolean resolved;
+ protected Boolean useClassConstructor;
+
+ public Node(Tag tag, Mark startMark, Mark endMark) {
+ setTag(tag);
+ this.startMark = startMark;
+ this.endMark = endMark;
+ this.type = Object.class;
+ this.twoStepsConstruction = false;
+ this.resolved = true;
+ this.useClassConstructor = null;
+ this.inLineComments = null;
+ this.blockComments = null;
+ this.endComments = null;
+ }
+
+ /**
+ * Tag of this node.
+ * <p>
+ * Every node has a tag assigned. The tag is either local or global.
+ *
+ * @return Tag of this node.
+ */
+ public Tag getTag() {
+ return this.tag;
+ }
+
+ public Mark getEndMark() {
+ return endMark;
+ }
+
+ /**
+ * For error reporting.
+ *
+ * @see "class variable 'id' in PyYAML"
+ * @return scalar, sequence, mapping
+ */
+ public abstract NodeId getNodeId();
+
+ public Mark getStartMark() {
+ return startMark;
+ }
+
+ public void setTag(Tag tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag in a Node is required.");
}
-
- public void setType(Class<? extends Object> type) {
- if (!type.isAssignableFrom(this.type)) {
- this.type = type;
- }
- }
-
- public void setTwoStepsConstruction(boolean twoStepsConstruction) {
- this.twoStepsConstruction = twoStepsConstruction;
- }
-
- /**
- * Indicates if this node must be constructed in two steps.
- * <p>
- * Two-step construction is required whenever a node is a child (direct or
- * indirect) of it self. That is, if a recursive structure is build using
- * anchors and aliases.
- * </p>
- * <p>
- * Set by {@link org.yaml.snakeyaml.composer.Composer}, used during the
- * construction process.
- * </p>
- * <p>
- * Only relevant during loading.
- * </p>
- *
- * @return <code>true</code> if the node is self referenced.
- */
- public boolean isTwoStepsConstruction() {
- return twoStepsConstruction;
- }
-
- @Override
- public final int hashCode() {
- return super.hashCode();
- }
-
- public boolean useClassConstructor() {
- if (useClassConstructor == null) {
- if (!tag.isSecondary() && isResolved() && !Object.class.equals(type)
- && !tag.equals(Tag.NULL)) {
- return true;
- } else if (tag.isCompatible(getType())) {
- // the tag is compatible with the runtime class
- // the tag will be ignored
- return true;
- } else {
- return false;
- }
- }
- return useClassConstructor.booleanValue();
+ this.tag = tag;
+ }
+
+ /**
+ * Node is only equal to itself
+ */
+ @Override
+ public final boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
+ public Class<? extends Object> getType() {
+ return type;
+ }
+
+ public void setType(Class<? extends Object> type) {
+ if (!type.isAssignableFrom(this.type)) {
+ this.type = type;
}
-
- public void setUseClassConstructor(Boolean useClassConstructor) {
- this.useClassConstructor = useClassConstructor;
- }
-
- /**
- * Indicates if the tag was added by
- * {@link org.yaml.snakeyaml.resolver.Resolver}.
- *
- * @return <code>true</code> if the tag of this node was resolved</code>
- */
- public boolean isResolved() {
- return resolved;
+ }
+
+ public void setTwoStepsConstruction(boolean twoStepsConstruction) {
+ this.twoStepsConstruction = twoStepsConstruction;
+ }
+
+ /**
+ * Indicates if this node must be constructed in two steps.
+ * <p>
+ * Two-step construction is required whenever a node is a child (direct or indirect) of it self.
+ * That is, if a recursive structure is build using anchors and aliases.
+ * </p>
+ * <p>
+ * Set by {@link org.yaml.snakeyaml.composer.Composer}, used during the construction process.
+ * </p>
+ * <p>
+ * Only relevant during loading.
+ * </p>
+ *
+ * @return <code>true</code> if the node is self referenced.
+ */
+ public boolean isTwoStepsConstruction() {
+ return twoStepsConstruction;
+ }
+
+ @Override
+ public final int hashCode() {
+ return super.hashCode();
+ }
+
+ public boolean useClassConstructor() {
+ if (useClassConstructor == null) {
+ // the tag is compatible with the runtime class
+ // the tag will be ignored
+ if (!tag.isSecondary() && resolved && !Object.class.equals(type) && !tag.equals(Tag.NULL)) {
+ return true;
+ } else {
+ return tag.isCompatible(getType());
+ }
}
+ return useClassConstructor.booleanValue();
+ }
+
+ public void setUseClassConstructor(Boolean useClassConstructor) {
+ this.useClassConstructor = useClassConstructor;
+ }
+
+ /**
+ * Indicates if the tag was added by {@link org.yaml.snakeyaml.resolver.Resolver}.
+ *
+ * @return true if the tag of this node was resolved
+ *
+ * @deprecated Since v1.22. Absent in immediately prior versions, but present previously. Restored
+ * deprecated for backwards compatibility.
+ */
+ @Deprecated
+ public boolean isResolved() {
+ return resolved;
+ }
+
+ public String getAnchor() {
+ return anchor;
+ }
+
+ public void setAnchor(String anchor) {
+ this.anchor = anchor;
+ }
+
+ /**
+ * The ordered list of in-line comments. The first of which appears at the end of the line
+ * respresent by this node. The rest are in the following lines, indented per the Spec to indicate
+ * they are continuation of the inline comment.
+ *
+ * @return the comment line list.
+ */
+ public List<CommentLine> getInLineComments() {
+ return inLineComments;
+ }
+
+ public void setInLineComments(List<CommentLine> inLineComments) {
+ this.inLineComments = inLineComments;
+ }
+
+ /**
+ * The ordered list of blank lines and block comments (full line) that appear before this node.
+ *
+ * @return the comment line list.
+ */
+ public List<CommentLine> getBlockComments() {
+ return blockComments;
+ }
+
+ public void setBlockComments(List<CommentLine> blockComments) {
+ this.blockComments = blockComments;
+ }
+
+ /**
+ * The ordered list of blank lines and block comments (full line) that appear AFTER this node.
+ * <p>
+ * NOTE: these comment should occur only in the last node in a document, when walking the node
+ * tree "in order"
+ *
+ * @return the comment line list.
+ */
+ public List<CommentLine> getEndComments() {
+ return endComments;
+ }
+
+ public void setEndComments(List<CommentLine> endComments) {
+ this.endComments = endComments;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/NodeId.java b/src/main/java/org/yaml/snakeyaml/nodes/NodeId.java
index 178e26d0..f9bcef7b 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/NodeId.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/NodeId.java
@@ -1,23 +1,21 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
/**
- * Enum for the three basic YAML types: scalar, sequence and mapping.
+ * Enum for the basic YAML types: scalar, sequence, mapping or anchor.
*/
public enum NodeId {
- scalar, sequence, mapping, anchor;
+ scalar, sequence, mapping, anchor
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/NodeTuple.java b/src/main/java/org/yaml/snakeyaml/nodes/NodeTuple.java
index 3e3cb36e..1380a5f6 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/NodeTuple.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/NodeTuple.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
@@ -20,36 +18,37 @@ package org.yaml.snakeyaml.nodes;
*/
public final class NodeTuple {
- private Node keyNode;
- private Node valueNode;
+ private final Node keyNode;
+ private final Node valueNode;
- public NodeTuple(Node keyNode, Node valueNode) {
- if (keyNode == null || valueNode == null) {
- throw new NullPointerException("Nodes must be provided.");
- }
- this.keyNode = keyNode;
- this.valueNode = valueNode;
+ public NodeTuple(Node keyNode, Node valueNode) {
+ if (keyNode == null || valueNode == null) {
+ throw new NullPointerException("Nodes must be provided.");
}
+ this.keyNode = keyNode;
+ this.valueNode = valueNode;
+ }
- /**
- * Key node.
- */
- public Node getKeyNode() {
- return keyNode;
- }
+ /**
+ * Key node.
+ *
+ * @return the node used as key
+ */
+ public Node getKeyNode() {
+ return keyNode;
+ }
- /**
- * Value node.
- *
- * @return value
- */
- public Node getValueNode() {
- return valueNode;
- }
+ /**
+ * Value node.
+ *
+ * @return node used as value
+ */
+ public Node getValueNode() {
+ return valueNode;
+ }
- @Override
- public String toString() {
- return "<NodeTuple keyNode=" + keyNode.toString() + "; valueNode=" + valueNode.toString()
- + ">";
- }
+ @Override
+ public String toString() {
+ return "<NodeTuple keyNode=" + keyNode + "; valueNode=" + valueNode + ">";
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/ScalarNode.java b/src/main/java/org/yaml/snakeyaml/nodes/ScalarNode.java
index 9120d0ee..61212df0 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/ScalarNode.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/ScalarNode.java
@@ -1,20 +1,19 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
@@ -24,51 +23,97 @@ import org.yaml.snakeyaml.error.Mark;
* </p>
*/
public class ScalarNode extends Node {
- private Character style;
- private String value;
- public ScalarNode(Tag tag, String value, Mark startMark, Mark endMark, Character style) {
- this(tag, true, value, startMark, endMark, style);
- }
+ private final DumperOptions.ScalarStyle style;
+ private final String value;
- public ScalarNode(Tag tag, boolean resolved, String value, Mark startMark, Mark endMark,
- Character style) {
- super(tag, startMark, endMark);
- if (value == null) {
- throw new NullPointerException("value in a Node is required.");
- }
- this.value = value;
- this.style = style;
- this.resolved = resolved;
- }
+ public ScalarNode(Tag tag, String value, Mark startMark, Mark endMark,
+ DumperOptions.ScalarStyle style) {
+ this(tag, true, value, startMark, endMark, style);
+ }
- /**
- * Get scalar style of this node.
- *
- * @see org.yaml.snakeyaml.events.ScalarEvent
- * @see <a href="http://yaml.org/spec/1.1/#id903915">Chapter 9. Scalar
- * Styles</a>
- */
- public Character getStyle() {
- return style;
+ public ScalarNode(Tag tag, boolean resolved, String value, Mark startMark, Mark endMark,
+ DumperOptions.ScalarStyle style) {
+ super(tag, startMark, endMark);
+ if (value == null) {
+ throw new NullPointerException("value in a Node is required.");
}
-
- @Override
- public NodeId getNodeId() {
- return NodeId.scalar;
+ this.value = value;
+ if (style == null) {
+ throw new NullPointerException("Scalar style must be provided.");
}
+ this.style = style;
+ this.resolved = resolved;
+ }
- /**
- * Value of this scalar.
- *
- * @return Scalar's value.
- */
- public String getValue() {
- return value;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.ScalarStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link ScalarNode#ScalarNode(Tag, String, Mark, Mark,
+ * org.yaml.snakeyaml.DumperOptions.ScalarStyle) }.
+ */
+ @Deprecated
+ public ScalarNode(Tag tag, String value, Mark startMark, Mark endMark, Character style) {
+ this(tag, value, startMark, endMark, DumperOptions.ScalarStyle.createStyle(style));
+ }
- public String toString() {
- return "<" + this.getClass().getName() + " (tag=" + getTag() + ", value=" + getValue()
- + ")>";
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.ScalarStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link ScalarNode#ScalarNode(Tag, boolean, String,
+ * Mark, Mark, org.yaml.snakeyaml.DumperOptions.ScalarStyle) }.
+ */
+ @Deprecated
+ public ScalarNode(Tag tag, boolean resolved, String value, Mark startMark, Mark endMark,
+ Character style) {
+ this(tag, resolved, value, startMark, endMark, DumperOptions.ScalarStyle.createStyle(style));
+ }
+
+ /**
+ * Get scalar style of this node.
+ *
+ * @see org.yaml.snakeyaml.events.ScalarEvent
+ * @see <a href="http://yaml.org/spec/1.1/#id903915">Chapter 9. Scalar Styles</a>
+ * @return style of this scalar node
+ * @deprecated use getScalarStyle instead
+ */
+ @Deprecated
+ public Character getStyle() {
+ return style.getChar();
+ }
+
+ /**
+ * Get scalar style of this node.
+ *
+ * @see org.yaml.snakeyaml.events.ScalarEvent
+ * @see <a href="http://yaml.org/spec/1.1/#id903915">Chapter 9. Scalar Styles</a>
+ * @return style of this scalar node
+ */
+ public DumperOptions.ScalarStyle getScalarStyle() {
+ return style;
+ }
+
+ @Override
+ public NodeId getNodeId() {
+ return NodeId.scalar;
+ }
+
+ /**
+ * Value of this scalar.
+ *
+ * @return Scalar's value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ public String toString() {
+ return "<" + this.getClass().getName() + " (tag=" + getTag() + ", value=" + getValue() + ")>";
+ }
+
+ public boolean isPlain() {
+ return style == DumperOptions.ScalarStyle.PLAIN;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/SequenceNode.java b/src/main/java/org/yaml/snakeyaml/nodes/SequenceNode.java
index 5e2f6b40..745268da 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/SequenceNode.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/SequenceNode.java
@@ -1,22 +1,20 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
import java.util.List;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
/**
@@ -25,45 +23,70 @@ import org.yaml.snakeyaml.error.Mark;
* A sequence is a ordered collection of nodes.
* </p>
*/
-public class SequenceNode extends CollectionNode {
- final private List<Node> value;
+public class SequenceNode extends CollectionNode<Node> {
- public SequenceNode(Tag tag, boolean resolved, List<Node> value, Mark startMark, Mark endMark,
- Boolean flowStyle) {
- super(tag, startMark, endMark, flowStyle);
- if (value == null) {
- throw new NullPointerException("value in a Node is required.");
- }
- this.value = value;
- this.resolved = resolved;
- }
+ private final List<Node> value;
- public SequenceNode(Tag tag, List<Node> value, Boolean flowStyle) {
- this(tag, true, value, null, null, flowStyle);
+ public SequenceNode(Tag tag, boolean resolved, List<Node> value, Mark startMark, Mark endMark,
+ DumperOptions.FlowStyle flowStyle) {
+ super(tag, startMark, endMark, flowStyle);
+ if (value == null) {
+ throw new NullPointerException("value in a Node is required.");
}
+ this.value = value;
+ this.resolved = resolved;
+ }
- @Override
- public NodeId getNodeId() {
- return NodeId.sequence;
- }
+ public SequenceNode(Tag tag, List<Node> value, DumperOptions.FlowStyle flowStyle) {
+ this(tag, true, value, null, null, flowStyle);
+ }
- /**
- * Returns the elements in this sequence.
- *
- * @return Nodes in the specified order.
- */
- public List<Node> getValue() {
- return value;
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.SequenceStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link SequenceNode#SequenceNode(Tag, List<Node>,
+ * org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public SequenceNode(Tag tag, List<Node> value, Boolean style) {
+ this(tag, value, DumperOptions.FlowStyle.fromBoolean(style));
+ }
- public void setListType(Class<? extends Object> listType) {
- for (Node node : value) {
- node.setType(listType);
- }
- }
+ /*
+ * Existed in older versions but replaced with {@link DumperOptions.SequenceStyle}-based
+ * constructor. Restored in v1.22 for backwards compatibility.
+ *
+ * @deprecated Since restored in v1.22. Use {@link SequenceNode#SequenceNode(Tag, boolean,
+ * List<Node>, Mark, Mark, org.yaml.snakeyaml.DumperOptions.FlowStyle) }.
+ */
+ @Deprecated
+ public SequenceNode(Tag tag, boolean resolved, List<Node> value, Mark startMark, Mark endMark,
+ Boolean style) {
+ this(tag, resolved, value, startMark, endMark, DumperOptions.FlowStyle.fromBoolean(style));
+ }
+
+ @Override
+ public NodeId getNodeId() {
+ return NodeId.sequence;
+ }
- public String toString() {
- return "<" + this.getClass().getName() + " (tag=" + getTag() + ", value=" + getValue()
- + ")>";
+ /**
+ * Returns the elements in this sequence.
+ *
+ * @return Nodes in the specified order.
+ */
+ public List<Node> getValue() {
+ return value;
+ }
+
+ public void setListType(Class<? extends Object> listType) {
+ for (Node node : value) {
+ node.setType(listType);
}
+ }
+
+ public String toString() {
+ return "<" + this.getClass().getName() + " (tag=" + getTag() + ", value=" + getValue() + ")>";
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/nodes/Tag.java b/src/main/java/org/yaml/snakeyaml/nodes/Tag.java
index 913e1f5b..dbb6f5a8 100644
--- a/src/main/java/org/yaml/snakeyaml/nodes/Tag.java
+++ b/src/main/java/org/yaml/snakeyaml/nodes/Tag.java
@@ -1,172 +1,175 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.nodes;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
-import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.util.UriEncoder;
-public final class Tag implements Comparable<Tag> {
- public static final String PREFIX = "tag:yaml.org,2002:";
- public static final Tag YAML = new Tag(PREFIX + "yaml");
- public static final Tag MERGE = new Tag(PREFIX + "merge");
- public static final Tag SET = new Tag(PREFIX + "set");
- public static final Tag PAIRS = new Tag(PREFIX + "pairs");
- public static final Tag OMAP = new Tag(PREFIX + "omap");
- public static final Tag BINARY = new Tag(PREFIX + "binary");
- public static final Tag INT = new Tag(PREFIX + "int");
- public static final Tag FLOAT = new Tag(PREFIX + "float");
- public static final Tag TIMESTAMP = new Tag(PREFIX + "timestamp");
- public static final Tag BOOL = new Tag(PREFIX + "bool");
- public static final Tag NULL = new Tag(PREFIX + "null");
- public static final Tag STR = new Tag(PREFIX + "str");
- public static final Tag SEQ = new Tag(PREFIX + "seq");
- public static final Tag MAP = new Tag(PREFIX + "map");
- public static final Map<Tag, Set<Class<?>>> COMPATIBILITY_MAP;
- static {
- COMPATIBILITY_MAP = new HashMap<Tag, Set<Class<?>>>();
- Set<Class<?>> floatSet = new HashSet<Class<?>>();
- floatSet.add(Double.class);
- floatSet.add(Float.class);
- floatSet.add(BigDecimal.class);
- COMPATIBILITY_MAP.put(FLOAT, floatSet);
- //
- Set<Class<?>> intSet = new HashSet<Class<?>>();
- intSet.add(Integer.class);
- intSet.add(Long.class);
- intSet.add(BigInteger.class);
- COMPATIBILITY_MAP.put(INT, intSet);
- //
- Set<Class<?>> timestampSet = new HashSet<Class<?>>();
- timestampSet.add(Date.class);
- timestampSet.add(java.sql.Date.class);
- timestampSet.add(Timestamp.class);
- COMPATIBILITY_MAP.put(TIMESTAMP, timestampSet);
- }
-
- private final String value;
- private boolean secondary = false; // see http://www.yaml.org/refcard.html
-
- public Tag(String tag) {
- if (tag == null) {
- throw new NullPointerException("Tag must be provided.");
- } else if (tag.length() == 0) {
- throw new IllegalArgumentException("Tag must not be empty.");
- } else if (tag.trim().length() != tag.length()) {
- throw new IllegalArgumentException("Tag must not contain leading or trailing spaces.");
- }
- this.value = UriEncoder.encode(tag);
- this.secondary = !tag.startsWith(PREFIX);
+public final class Tag {
+
+ public static final String PREFIX = "tag:yaml.org,2002:";
+ public static final Tag YAML = new Tag(PREFIX + "yaml");
+ public static final Tag MERGE = new Tag(PREFIX + "merge");
+ public static final Tag SET = new Tag(PREFIX + "set");
+ public static final Tag PAIRS = new Tag(PREFIX + "pairs");
+ public static final Tag OMAP = new Tag(PREFIX + "omap");
+ public static final Tag BINARY = new Tag(PREFIX + "binary");
+ public static final Tag INT = new Tag(PREFIX + "int");
+ public static final Tag FLOAT = new Tag(PREFIX + "float");
+ public static final Tag TIMESTAMP = new Tag(PREFIX + "timestamp");
+ public static final Tag BOOL = new Tag(PREFIX + "bool");
+ public static final Tag NULL = new Tag(PREFIX + "null");
+ public static final Tag STR = new Tag(PREFIX + "str");
+ public static final Tag SEQ = new Tag(PREFIX + "seq");
+ public static final Tag MAP = new Tag(PREFIX + "map");
+ // For use to indicate a DUMMY node that contains comments, when there is no other (empty
+ // document)
+ public static final Tag COMMENT = new Tag(PREFIX + "comment");
+ private static final Map<Tag, Set<Class<?>>> COMPATIBILITY_MAP;
+
+ static {
+ COMPATIBILITY_MAP = new HashMap<Tag, Set<Class<?>>>();
+ Set<Class<?>> floatSet = new HashSet<Class<?>>();
+ floatSet.add(Double.class);
+ floatSet.add(Float.class);
+ floatSet.add(BigDecimal.class);
+ COMPATIBILITY_MAP.put(FLOAT, floatSet);
+ //
+ Set<Class<?>> intSet = new HashSet<Class<?>>();
+ intSet.add(Integer.class);
+ intSet.add(Long.class);
+ intSet.add(BigInteger.class);
+ COMPATIBILITY_MAP.put(INT, intSet);
+ //
+ Set<Class<?>> timestampSet = new HashSet<Class<?>>();
+ timestampSet.add(Date.class);
+
+ // java.sql is a separate module since jigsaw was introduced in java9
+ try {
+ timestampSet.add(Class.forName("java.sql.Date"));
+ timestampSet.add(Class.forName("java.sql.Timestamp"));
+ } catch (ClassNotFoundException ignored) {
+ // ignore - we are running in a module path without java.sql
}
- public Tag(Class<? extends Object> clazz) {
- if (clazz == null) {
- throw new NullPointerException("Class for tag must be provided.");
- }
- this.value = Tag.PREFIX + UriEncoder.encode(clazz.getName());
- }
+ COMPATIBILITY_MAP.put(TIMESTAMP, timestampSet);
+ }
- //TODO to be removed ?
- public Tag(URI uri) {
- if (uri == null) {
- throw new NullPointerException("URI for tag must be provided.");
- }
- this.value = uri.toASCIIString();
- }
-
- public boolean isSecondary() {
- return secondary;
- }
+ private final String value;
+ private boolean secondary = false; // see http://www.yaml.org/refcard.html
- public String getValue() {
- return value;
+ public Tag(String tag) {
+ if (tag == null) {
+ throw new NullPointerException("Tag must be provided.");
+ } else if (tag.length() == 0) {
+ throw new IllegalArgumentException("Tag must not be empty.");
+ } else if (tag.trim().length() != tag.length()) {
+ throw new IllegalArgumentException("Tag must not contain leading or trailing spaces.");
}
+ this.value = UriEncoder.encode(tag);
+ this.secondary = !tag.startsWith(PREFIX);
+ }
- public boolean startsWith(String prefix) {
- return value.startsWith(prefix);
+ public Tag(Class<? extends Object> clazz) {
+ if (clazz == null) {
+ throw new NullPointerException("Class for tag must be provided.");
}
-
- public String getClassName() {
- if (!value.startsWith(Tag.PREFIX)) {
- throw new YAMLException("Invalid tag: " + value);
- }
- return UriEncoder.decode(value.substring(Tag.PREFIX.length()));
+ this.value = Tag.PREFIX + UriEncoder.encode(clazz.getName());
+ }
+
+ /**
+ * @deprecated - it will be removed
+ * @param uri - URI to be encoded as tag value
+ */
+ @Deprecated
+ public Tag(URI uri) {
+ if (uri == null) {
+ throw new NullPointerException("URI for tag must be provided.");
}
+ this.value = uri.toASCIIString();
+ }
- public int getLength() {
- return value.length();
- }
+ public boolean isSecondary() {
+ return secondary;
+ }
- @Override
- public String toString() {
- return value;
- }
+ public String getValue() {
+ return value;
+ }
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Tag) {
- return value.equals(((Tag) obj).getValue());
- } else
- return false;
- }
+ public boolean startsWith(String prefix) {
+ return value.startsWith(prefix);
+ }
- @Override
- public int hashCode() {
- return value.hashCode();
+ public String getClassName() {
+ if (!value.startsWith(Tag.PREFIX)) {
+ throw new YAMLException("Invalid tag: " + value);
}
-
- /**
- * Java has more then 1 class compatible with a language-independent tag
- * (!!int, !!float, !!timestamp etc)
- *
- * @param clazz
- * - Class to check compatibility
- * @return true when the Class can be represented by this
- * language-independent tag
- */
- public boolean isCompatible(Class<?> clazz) {
- Set<Class<?>> set = COMPATIBILITY_MAP.get(this);
- if (set != null) {
- return set.contains(clazz);
- } else {
- return false;
- }
+ return UriEncoder.decode(value.substring(Tag.PREFIX.length()));
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Tag) {
+ return value.equals(((Tag) obj).getValue());
+ } else {
+ return false;
}
-
- /**
- * Check whether this tag matches the global tag for the Class
- *
- * @param clazz
- * - Class to check
- * @return true when the this tag can be used as a global tag for the Class
- */
- public boolean matches(Class<? extends Object> clazz) {
- return value.equals(Tag.PREFIX + clazz.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ /**
+ * Java has more then 1 class compatible with a language-independent tag (!!int, !!float,
+ * !!timestamp etc)
+ *
+ * @param clazz - Class to check compatibility
+ * @return true when the Class can be represented by this language-independent tag
+ */
+ public boolean isCompatible(Class<?> clazz) {
+ Set<Class<?>> set = COMPATIBILITY_MAP.get(this);
+ if (set != null) {
+ return set.contains(clazz);
+ } else {
+ return false;
}
+ }
+
+ /**
+ * Check whether this tag matches the global tag for the Class
+ *
+ * @param clazz - Class to check
+ * @return true when the this tag can be used as a global tag for the Class
+ */
+ public boolean matches(Class<? extends Object> clazz) {
+ return value.equals(Tag.PREFIX + clazz.getName());
+ }
- public int compareTo(Tag o) {
- return value.compareTo(o.getValue());
- }
}
diff --git a/src/main/java/org/yaml/snakeyaml/parser/Parser.java b/src/main/java/org/yaml/snakeyaml/parser/Parser.java
index 8c1bf169..b0e96ea4 100644
--- a/src/main/java/org/yaml/snakeyaml/parser/Parser.java
+++ b/src/main/java/org/yaml/snakeyaml/parser/Parser.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.parser;
@@ -20,46 +18,40 @@ import org.yaml.snakeyaml.events.Event;
/**
* This interface represents an input stream of {@link Event Events}.
* <p>
- * The parser and the scanner form together the 'Parse' step in the loading
- * process (see chapter 3.1 of the <a href="http://yaml.org/spec/1.1/">YAML
- * Specification</a>).
+ * The parser and the scanner form together the 'Parse' step in the loading process (see chapter 3.1
+ * of the <a href="http://yaml.org/spec/1.1/">YAML Specification</a>).
* </p>
- *
+ *
* @see org.yaml.snakeyaml.events.Event
*/
public interface Parser {
- /**
- * Check if the next event is one of the given type.
- *
- * @param choice
- * Event ID.
- * @return <code>true</code> if the next event can be assigned to a variable
- * of the given type. Returns <code>false</code> if no more events
- * are available.
- * @throws ParserException
- * Thrown in case of malformed input.
- */
- public boolean checkEvent(Event.ID choice);
+ /**
+ * Check if the next event is one of the given type.
+ *
+ * @param choice Event ID.
+ * @return <code>true</code> if the next event can be assigned to a variable of the given type.
+ * Returns <code>false</code> if no more events are available.
+ * @throws ParserException Thrown in case of malformed input.
+ */
+ boolean checkEvent(Event.ID choice);
- /**
- * Return the next event, but do not delete it from the stream.
- *
- * @return The event that will be returned on the next call to
- * {@link #getEvent}
- * @throws ParserException
- * Thrown in case of malformed input.
- */
- public Event peekEvent();
+ /**
+ * Return the next event, but do not delete it from the stream.
+ *
+ * @return The event that will be returned on the next call to {@link #getEvent}
+ * @throws ParserException Thrown in case of malformed input.
+ */
+ Event peekEvent();
- /**
- * Returns the next event.
- * <p>
- * The event will be removed from the stream.
- * </p>
- *
- * @throws ParserException
- * Thrown in case of malformed input.
- */
- public Event getEvent();
+ /**
+ * Returns the next event.
+ * <p>
+ * The event will be removed from the stream.
+ * </p>
+ *
+ * @return the next parsed event
+ * @throws ParserException Thrown in case of malformed input.
+ */
+ Event getEvent();
}
diff --git a/src/main/java/org/yaml/snakeyaml/parser/ParserException.java b/src/main/java/org/yaml/snakeyaml/parser/ParserException.java
index fd4b1f1c..90e49c08 100644
--- a/src/main/java/org/yaml/snakeyaml/parser/ParserException.java
+++ b/src/main/java/org/yaml/snakeyaml/parser/ParserException.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.parser;
@@ -19,26 +17,21 @@ import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.MarkedYAMLException;
/**
- * Exception thrown by the {@link Parser} implementations in case of malformed
- * input.
+ * Exception thrown by the {@link Parser} implementations in case of malformed input.
*/
public class ParserException extends MarkedYAMLException {
- private static final long serialVersionUID = -2349253802798398038L;
- /**
- * Constructs an instance.
- *
- * @param context
- * Part of the input document in which vicinity the problem
- * occurred.
- * @param contextMark
- * Position of the <code>context</code> within the document.
- * @param problem
- * Part of the input document that caused the problem.
- * @param problemMark
- * Position of the <code>problem</code>. within the document.
- */
- public ParserException(String context, Mark contextMark, String problem, Mark problemMark) {
- super(context, contextMark, problem, problemMark, null, null);
- }
+ private static final long serialVersionUID = -2349253802798398038L;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param context Part of the input document in which vicinity the problem occurred.
+ * @param contextMark Position of the <code>context</code> within the document.
+ * @param problem Part of the input document that caused the problem.
+ * @param problemMark Position of the <code>problem</code>. within the document.
+ */
+ public ParserException(String context, Mark contextMark, String problem, Mark problemMark) {
+ super(context, contextMark, problem, problemMark, null, null);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java b/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java
index 7fea2eec..7c66434f 100644
--- a/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java
+++ b/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java
@@ -1,28 +1,30 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.parser;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.Version;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
+import org.yaml.snakeyaml.events.CommentEvent;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
@@ -41,6 +43,7 @@ import org.yaml.snakeyaml.scanner.ScannerImpl;
import org.yaml.snakeyaml.tokens.AliasToken;
import org.yaml.snakeyaml.tokens.AnchorToken;
import org.yaml.snakeyaml.tokens.BlockEntryToken;
+import org.yaml.snakeyaml.tokens.CommentToken;
import org.yaml.snakeyaml.tokens.DirectiveToken;
import org.yaml.snakeyaml.tokens.ScalarToken;
import org.yaml.snakeyaml.tokens.StreamEndToken;
@@ -109,686 +112,863 @@ import org.yaml.snakeyaml.util.ArrayStack;
* flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
* flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
* </pre>
- *
- * Since writing a recursive-descendant parser is a straightforward task, we do
- * not give many comments here.
+ *
+ * Since writing a recursive-descendant parser is a straightforward task, we do not give many
+ * comments here.
*/
public class ParserImpl implements Parser {
- private static final Map<String, String> DEFAULT_TAGS = new HashMap<String, String>();
- static {
- DEFAULT_TAGS.put("!", "!");
- DEFAULT_TAGS.put("!!", Tag.PREFIX);
- }
-
- protected final Scanner scanner;
- private Event currentEvent;
- private final ArrayStack<Production> states;
- private final ArrayStack<Mark> marks;
- private Production state;
- private VersionTagsTuple directives;
-
- public ParserImpl(StreamReader reader) {
- this(new ScannerImpl(reader));
- }
-
- public ParserImpl(Scanner scanner) {
- this.scanner = scanner;
- currentEvent = null;
- directives = new VersionTagsTuple(null, new HashMap<String, String>(DEFAULT_TAGS));
- states = new ArrayStack<Production>(100);
- marks = new ArrayStack<Mark>(10);
- state = new ParseStreamStart();
- }
-
- /**
- * Check the type of the next event.
- */
- public boolean checkEvent(Event.ID choice) {
- peekEvent();
- return currentEvent != null && currentEvent.is(choice);
- }
-
- /**
- * Get the next event.
- */
- public Event peekEvent() {
- if (currentEvent == null) {
- if (state != null) {
- currentEvent = state.produce();
- }
- }
- return currentEvent;
- }
-
- /**
- * Get the next event and proceed further.
- */
- public Event getEvent() {
- peekEvent();
- Event value = currentEvent;
- currentEvent = null;
- return value;
- }
-
- /**
- * <pre>
- * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
- * implicit_document ::= block_node DOCUMENT-END*
- * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
- * </pre>
- */
- private class ParseStreamStart implements Production {
- public Event produce() {
- // Parse the stream start.
- StreamStartToken token = (StreamStartToken) scanner.getToken();
- Event event = new StreamStartEvent(token.getStartMark(), token.getEndMark());
- // Prepare the next state.
- state = new ParseImplicitDocumentStart();
- return event;
- }
- }
-
- private class ParseImplicitDocumentStart implements Production {
- public Event produce() {
- // Parse an implicit document.
- if (!scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart, Token.ID.StreamEnd)) {
- directives = new VersionTagsTuple(null, DEFAULT_TAGS);
- Token token = scanner.peekToken();
- Mark startMark = token.getStartMark();
- Mark endMark = startMark;
- Event event = new DocumentStartEvent(startMark, endMark, false, null, null);
- // Prepare the next state.
- states.push(new ParseDocumentEnd());
- state = new ParseBlockNode();
- return event;
- } else {
- Production p = new ParseDocumentStart();
- return p.produce();
- }
- }
- }
-
- private class ParseDocumentStart implements Production {
- public Event produce() {
- // Parse any extra document end indicators.
- while (scanner.checkToken(Token.ID.DocumentEnd)) {
- scanner.getToken();
- }
- // Parse an explicit document.
- Event event;
- if (!scanner.checkToken(Token.ID.StreamEnd)) {
- Token token = scanner.peekToken();
- Mark startMark = token.getStartMark();
- VersionTagsTuple tuple = processDirectives();
- if (!scanner.checkToken(Token.ID.DocumentStart)) {
- throw new ParserException(null, null, "expected '<document start>', but found "
- + scanner.peekToken().getTokenId(), scanner.peekToken().getStartMark());
- }
- token = scanner.getToken();
- Mark endMark = token.getEndMark();
- event = new DocumentStartEvent(startMark, endMark, true, tuple.getVersion(),
- tuple.getTags());
- states.push(new ParseDocumentEnd());
- state = new ParseDocumentContent();
- } else {
- // Parse the end of the stream.
- StreamEndToken token = (StreamEndToken) scanner.getToken();
- event = new StreamEndEvent(token.getStartMark(), token.getEndMark());
- if (!states.isEmpty()) {
- throw new YAMLException("Unexpected end of stream. States left: " + states);
- }
- if (!marks.isEmpty()) {
- throw new YAMLException("Unexpected end of stream. Marks left: " + marks);
- }
- state = null;
- }
- return event;
- }
- }
-
- private class ParseDocumentEnd implements Production {
- public Event produce() {
- // Parse the document end.
- Token token = scanner.peekToken();
- Mark startMark = token.getStartMark();
- Mark endMark = startMark;
- boolean explicit = false;
- if (scanner.checkToken(Token.ID.DocumentEnd)) {
- token = scanner.getToken();
- endMark = token.getEndMark();
- explicit = true;
- }
- Event event = new DocumentEndEvent(startMark, endMark, explicit);
- // Prepare the next state.
- state = new ParseDocumentStart();
- return event;
- }
- }
- private class ParseDocumentContent implements Production {
- public Event produce() {
- Event event;
- if (scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart,
- Token.ID.DocumentEnd, Token.ID.StreamEnd)) {
- event = processEmptyScalar(scanner.peekToken().getStartMark());
- state = states.pop();
- return event;
- } else {
- Production p = new ParseBlockNode();
- return p.produce();
- }
+ private static final Map<String, String> DEFAULT_TAGS = new HashMap<String, String>();
+
+ static {
+ DEFAULT_TAGS.put("!", "!");
+ DEFAULT_TAGS.put("!!", Tag.PREFIX);
+ }
+
+ protected final Scanner scanner;
+ private Event currentEvent;
+ private final ArrayStack<Production> states;
+ private final ArrayStack<Mark> marks;
+ private Production state;
+ private VersionTagsTuple directives;
+
+ public ParserImpl(StreamReader reader) {
+ this(new ScannerImpl(reader));
+ }
+
+ @Deprecated
+ public ParserImpl(StreamReader reader, boolean parseComments) {
+ this(new ScannerImpl(reader, new LoaderOptions().setProcessComments(parseComments)));
+ }
+
+ public ParserImpl(StreamReader reader, LoaderOptions options) {
+ this(new ScannerImpl(reader, options));
+ }
+
+ public ParserImpl(Scanner scanner) {
+ this.scanner = scanner;
+ currentEvent = null;
+ directives = new VersionTagsTuple(null, new HashMap<String, String>(DEFAULT_TAGS));
+ states = new ArrayStack<Production>(100);
+ marks = new ArrayStack<Mark>(10);
+ state = new ParseStreamStart();
+ }
+
+ /**
+ * Check the type of the next event.
+ */
+ public boolean checkEvent(Event.ID choice) {
+ peekEvent();
+ return currentEvent != null && currentEvent.is(choice);
+ }
+
+ /**
+ * Get the next event.
+ */
+ public Event peekEvent() {
+ if (currentEvent == null) {
+ if (state != null) {
+ currentEvent = state.produce();
+ }
+ }
+ return currentEvent;
+ }
+
+ /**
+ * Get the next event and proceed further.
+ */
+ public Event getEvent() {
+ peekEvent();
+ Event value = currentEvent;
+ currentEvent = null;
+ return value;
+ }
+
+ private CommentEvent produceCommentEvent(CommentToken token) {
+ Mark startMark = token.getStartMark();
+ Mark endMark = token.getEndMark();
+ String value = token.getValue();
+ CommentType type = token.getCommentType();
+
+ // state = state, that no change in state
+
+ return new CommentEvent(type, value, startMark, endMark);
+ }
+
+ /**
+ * <pre>
+ * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+ * implicit_document ::= block_node DOCUMENT-END*
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ * </pre>
+ */
+ private class ParseStreamStart implements Production {
+
+ public Event produce() {
+ // Parse the stream start.
+ StreamStartToken token = (StreamStartToken) scanner.getToken();
+ Event event = new StreamStartEvent(token.getStartMark(), token.getEndMark());
+ // Prepare the next state.
+ state = new ParseImplicitDocumentStart();
+ return event;
+ }
+ }
+
+ private class ParseImplicitDocumentStart implements Production {
+
+ public Event produce() {
+ // Parse an implicit document.
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseImplicitDocumentStart();
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (!scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart, Token.ID.StreamEnd)) {
+ Token token = scanner.peekToken();
+ Mark startMark = token.getStartMark();
+ Mark endMark = startMark;
+ Event event = new DocumentStartEvent(startMark, endMark, false, null, null);
+ // Prepare the next state.
+ states.push(new ParseDocumentEnd());
+ state = new ParseBlockNode();
+ return event;
+ }
+ return new ParseDocumentStart().produce();
+ }
+ }
+
+ private class ParseDocumentStart implements Production {
+
+ public Event produce() {
+ // Parse any extra document end indicators.
+ while (scanner.checkToken(Token.ID.DocumentEnd)) {
+ scanner.getToken();
+ }
+ // Parse an explicit document.
+ Event event;
+ if (!scanner.checkToken(Token.ID.StreamEnd)) {
+ Token token = scanner.peekToken();
+ Mark startMark = token.getStartMark();
+ VersionTagsTuple tuple = processDirectives();
+ while (scanner.checkToken(Token.ID.Comment)) {
+ // TODO: till we figure out what todo with the comments
+ scanner.getToken();
}
- }
-
- @SuppressWarnings("unchecked")
- private VersionTagsTuple processDirectives() {
- Version yamlVersion = null;
- HashMap<String, String> tagHandles = new HashMap<String, String>();
- while (scanner.checkToken(Token.ID.Directive)) {
- @SuppressWarnings("rawtypes")
- DirectiveToken token = (DirectiveToken) scanner.getToken();
- if (token.getName().equals("YAML")) {
- if (yamlVersion != null) {
- throw new ParserException(null, null, "found duplicate YAML directive",
- token.getStartMark());
- }
- List<Integer> value = (List<Integer>) token.getValue();
- Integer major = value.get(0);
- if (major != 1) {
- throw new ParserException(null, null,
- "found incompatible YAML document (version 1.* is required)",
- token.getStartMark());
- }
- Integer minor = value.get(1);
- switch (minor) {
- case 0:
- yamlVersion = Version.V1_0;
- break;
-
- default:
- yamlVersion = Version.V1_1;
- break;
- }
- } else if (token.getName().equals("TAG")) {
- List<String> value = (List<String>) token.getValue();
- String handle = value.get(0);
- String prefix = value.get(1);
- if (tagHandles.containsKey(handle)) {
- throw new ParserException(null, null, "duplicate tag handle " + handle,
- token.getStartMark());
- }
- tagHandles.put(handle, prefix);
- }
+ if (!scanner.checkToken(Token.ID.StreamEnd)) {
+ if (!scanner.checkToken(Token.ID.DocumentStart)) {
+ throw new ParserException(null, null,
+ "expected '<document start>', but found '" + scanner.peekToken().getTokenId() + "'",
+ scanner.peekToken().getStartMark());
+ }
+ token = scanner.getToken();
+ Mark endMark = token.getEndMark();
+ event =
+ new DocumentStartEvent(startMark, endMark, true, tuple.getVersion(), tuple.getTags());
+ states.push(new ParseDocumentEnd());
+ state = new ParseDocumentContent();
+ return event;
}
- if (yamlVersion != null || !tagHandles.isEmpty()) {
- // directives in the document found - drop the previous
- for (String key : DEFAULT_TAGS.keySet()) {
- // do not overwrite re-defined tags
- if (!tagHandles.containsKey(key)) {
- tagHandles.put(key, DEFAULT_TAGS.get(key));
- }
- }
- directives = new VersionTagsTuple(yamlVersion, tagHandles);
+ }
+ // Parse the end of the stream.
+ StreamEndToken token = (StreamEndToken) scanner.getToken();
+ event = new StreamEndEvent(token.getStartMark(), token.getEndMark());
+ if (!states.isEmpty()) {
+ throw new YAMLException("Unexpected end of stream. States left: " + states);
+ }
+ if (!marks.isEmpty()) {
+ throw new YAMLException("Unexpected end of stream. Marks left: " + marks);
+ }
+ state = null;
+ return event;
+ }
+ }
+
+ private class ParseDocumentEnd implements Production {
+
+ public Event produce() {
+ // Parse the document end.
+ Token token = scanner.peekToken();
+ Mark startMark = token.getStartMark();
+ Mark endMark = startMark;
+ boolean explicit = false;
+ if (scanner.checkToken(Token.ID.DocumentEnd)) {
+ token = scanner.getToken();
+ endMark = token.getEndMark();
+ explicit = true;
+ }
+ Event event = new DocumentEndEvent(startMark, endMark, explicit);
+ // Prepare the next state.
+ state = new ParseDocumentStart();
+ return event;
+ }
+ }
+
+ private class ParseDocumentContent implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseDocumentContent();
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart, Token.ID.DocumentEnd,
+ Token.ID.StreamEnd)) {
+ Event event = processEmptyScalar(scanner.peekToken().getStartMark());
+ state = states.pop();
+ return event;
+ }
+ return new ParseBlockNode().produce();
+ }
+ }
+
+ /**
+ * https://yaml.org/spec/1.1/#id898785 says "If the document specifies no directives, it is parsed
+ * using the same settings as the previous document. If the document does specify any directives,
+ * all directives of previous documents, if any, are ignored." TODO the last statement is not
+ * respected (as in PyYAML, to work the same)
+ *
+ * @return directives to be applied for the current document
+ */
+ @SuppressWarnings("unchecked")
+ private VersionTagsTuple processDirectives() {
+ HashMap<String, String> tagHandles = new HashMap<String, String>(directives.getTags());
+ for (String key : DEFAULT_TAGS.keySet()) {
+ tagHandles.remove(key);
+ }
+ // keep only added tag handlers
+ directives = new VersionTagsTuple(null, tagHandles);
+ while (scanner.checkToken(Token.ID.Directive)) {
+ @SuppressWarnings("rawtypes")
+ DirectiveToken token = (DirectiveToken) scanner.getToken();
+ if (token.getName().equals("YAML")) {
+ if (directives.getVersion() != null) {
+ throw new ParserException(null, null, "found duplicate YAML directive",
+ token.getStartMark());
}
- return directives;
- }
-
- /**
- * <pre>
- * block_node_or_indentless_sequence ::= ALIAS
- * | properties (block_content | indentless_block_sequence)?
- * | block_content
- * | indentless_block_sequence
- * block_node ::= ALIAS
- * | properties block_content?
- * | block_content
- * flow_node ::= ALIAS
- * | properties flow_content?
- * | flow_content
- * properties ::= TAG ANCHOR? | ANCHOR TAG?
- * block_content ::= block_collection | flow_collection | SCALAR
- * flow_content ::= flow_collection | SCALAR
- * block_collection ::= block_sequence | block_mapping
- * flow_collection ::= flow_sequence | flow_mapping
- * </pre>
- */
-
- private class ParseBlockNode implements Production {
- public Event produce() {
- return parseNode(true, false);
+ List<Integer> value = (List<Integer>) token.getValue();
+ Integer major = value.get(0);
+ if (major != 1) {
+ throw new ParserException(null, null,
+ "found incompatible YAML document (version 1.* is required)", token.getStartMark());
}
- }
-
- private Event parseFlowNode() {
- return parseNode(false, false);
- }
-
- private Event parseBlockNodeOrIndentlessSequence() {
- return parseNode(true, true);
- }
-
- private Event parseNode(boolean block, boolean indentlessSequence) {
- Event event;
- Mark startMark = null;
- Mark endMark = null;
- Mark tagMark = null;
- if (scanner.checkToken(Token.ID.Alias)) {
- AliasToken token = (AliasToken) scanner.getToken();
- event = new AliasEvent(token.getValue(), token.getStartMark(), token.getEndMark());
- state = states.pop();
+ Integer minor = value.get(1);
+ // TODO refactor with ternary
+ if (minor == 0) {
+ directives = new VersionTagsTuple(Version.V1_0, tagHandles);
} else {
- String anchor = null;
- TagTuple tagTokenTag = null;
- if (scanner.checkToken(Token.ID.Anchor)) {
- AnchorToken token = (AnchorToken) scanner.getToken();
- startMark = token.getStartMark();
- endMark = token.getEndMark();
- anchor = token.getValue();
- if (scanner.checkToken(Token.ID.Tag)) {
- TagToken tagToken = (TagToken) scanner.getToken();
- tagMark = tagToken.getStartMark();
- endMark = tagToken.getEndMark();
- tagTokenTag = tagToken.getValue();
- }
- } else if (scanner.checkToken(Token.ID.Tag)) {
- TagToken tagToken = (TagToken) scanner.getToken();
- startMark = tagToken.getStartMark();
- tagMark = startMark;
- endMark = tagToken.getEndMark();
- tagTokenTag = tagToken.getValue();
- if (scanner.checkToken(Token.ID.Anchor)) {
- AnchorToken token = (AnchorToken) scanner.getToken();
- endMark = token.getEndMark();
- anchor = token.getValue();
- }
- }
- String tag = null;
- if (tagTokenTag != null) {
- String handle = tagTokenTag.getHandle();
- String suffix = tagTokenTag.getSuffix();
- if (handle != null) {
- if (!directives.getTags().containsKey(handle)) {
- throw new ParserException("while parsing a node", startMark,
- "found undefined tag handle " + handle, tagMark);
- }
- tag = directives.getTags().get(handle) + suffix;
- } else {
- tag = suffix;
- }
- }
- if (startMark == null) {
- startMark = scanner.peekToken().getStartMark();
- endMark = startMark;
- }
- event = null;
- boolean implicit = tag == null || tag.equals("!");
- if (indentlessSequence && scanner.checkToken(Token.ID.BlockEntry)) {
- endMark = scanner.peekToken().getEndMark();
- event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
- Boolean.FALSE);
- state = new ParseIndentlessSequenceEntry();
- } else {
- if (scanner.checkToken(Token.ID.Scalar)) {
- ScalarToken token = (ScalarToken) scanner.getToken();
- endMark = token.getEndMark();
- ImplicitTuple implicitValues;
- if ((token.getPlain() && tag == null) || "!".equals(tag)) {
- implicitValues = new ImplicitTuple(true, false);
- } else if (tag == null) {
- implicitValues = new ImplicitTuple(false, true);
- } else {
- implicitValues = new ImplicitTuple(false, false);
- }
- event = new ScalarEvent(anchor, tag, implicitValues, token.getValue(),
- startMark, endMark, token.getStyle());
- state = states.pop();
- } else if (scanner.checkToken(Token.ID.FlowSequenceStart)) {
- endMark = scanner.peekToken().getEndMark();
- event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
- Boolean.TRUE);
- state = new ParseFlowSequenceFirstEntry();
- } else if (scanner.checkToken(Token.ID.FlowMappingStart)) {
- endMark = scanner.peekToken().getEndMark();
- event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
- Boolean.TRUE);
- state = new ParseFlowMappingFirstKey();
- } else if (block && scanner.checkToken(Token.ID.BlockSequenceStart)) {
- endMark = scanner.peekToken().getStartMark();
- event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
- Boolean.FALSE);
- state = new ParseBlockSequenceFirstEntry();
- } else if (block && scanner.checkToken(Token.ID.BlockMappingStart)) {
- endMark = scanner.peekToken().getStartMark();
- event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
- Boolean.FALSE);
- state = new ParseBlockMappingFirstKey();
- } else if (anchor != null || tag != null) {
- // Empty scalars are allowed even if a tag or an anchor is
- // specified.
- event = new ScalarEvent(anchor, tag, new ImplicitTuple(implicit, false), "",
- startMark, endMark, (char) 0);
- state = states.pop();
- } else {
- String node;
- if (block) {
- node = "block";
- } else {
- node = "flow";
- }
- Token token = scanner.peekToken();
- throw new ParserException("while parsing a " + node + " node", startMark,
- "expected the node content, but found " + token.getTokenId(),
- token.getStartMark());
- }
- }
- }
- return event;
- }
-
- // block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
- // BLOCK-END
-
- private class ParseBlockSequenceFirstEntry implements Production {
- public Event produce() {
- Token token = scanner.getToken();
- marks.push(token.getStartMark());
- return new ParseBlockSequenceEntry().produce();
+ directives = new VersionTagsTuple(Version.V1_1, tagHandles);
}
- }
-
- private class ParseBlockSequenceEntry implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.BlockEntry)) {
- BlockEntryToken token = (BlockEntryToken) scanner.getToken();
- if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.BlockEnd)) {
- states.push(new ParseBlockSequenceEntry());
- return new ParseBlockNode().produce();
- } else {
- state = new ParseBlockSequenceEntry();
- return processEmptyScalar(token.getEndMark());
- }
- }
- if (!scanner.checkToken(Token.ID.BlockEnd)) {
- Token token = scanner.peekToken();
- throw new ParserException("while parsing a block collection", marks.pop(),
- "expected <block end>, but found " + token.getTokenId(),
- token.getStartMark());
- }
- Token token = scanner.getToken();
- Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
- state = states.pop();
- marks.pop();
- return event;
- }
- }
-
- // indentless_sequence ::= (BLOCK-ENTRY block_node?)+
-
- private class ParseIndentlessSequenceEntry implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.BlockEntry)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.Key, Token.ID.Value,
- Token.ID.BlockEnd)) {
- states.push(new ParseIndentlessSequenceEntry());
- return new ParseBlockNode().produce();
- } else {
- state = new ParseIndentlessSequenceEntry();
- return processEmptyScalar(token.getEndMark());
- }
- }
- Token token = scanner.peekToken();
- Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
- state = states.pop();
- return event;
+ } else if (token.getName().equals("TAG")) {
+ List<String> value = (List<String>) token.getValue();
+ String handle = value.get(0);
+ String prefix = value.get(1);
+ if (tagHandles.containsKey(handle)) {
+ throw new ParserException(null, null, "duplicate tag handle " + handle,
+ token.getStartMark());
}
- }
-
- private class ParseBlockMappingFirstKey implements Production {
- public Event produce() {
- Token token = scanner.getToken();
- marks.push(token.getStartMark());
- return new ParseBlockMappingKey().produce();
+ tagHandles.put(handle, prefix);
+ }
+ }
+ HashMap<String, String> detectedTagHandles = new HashMap<String, String>();
+ if (!tagHandles.isEmpty()) {
+ // copy from tagHandles
+ detectedTagHandles = new HashMap<String, String>(tagHandles);
+ }
+ // add default tag handlers to resolve tags
+ for (String key : DEFAULT_TAGS.keySet()) {
+ // do not overwrite re-defined tags
+ if (!tagHandles.containsKey(key)) {
+ tagHandles.put(key, DEFAULT_TAGS.get(key));
+ }
+ }
+ // data for the events (no default tags added)
+ return new VersionTagsTuple(directives.getVersion(), detectedTagHandles);
+ }
+
+ /**
+ * <pre>
+ * block_node_or_indentless_sequence ::= ALIAS
+ * | properties (block_content | indentless_block_sequence)?
+ * | block_content
+ * | indentless_block_sequence
+ * block_node ::= ALIAS
+ * | properties block_content?
+ * | block_content
+ * flow_node ::= ALIAS
+ * | properties flow_content?
+ * | flow_content
+ * properties ::= TAG ANCHOR? | ANCHOR TAG?
+ * block_content ::= block_collection | flow_collection | SCALAR
+ * flow_content ::= flow_collection | SCALAR
+ * block_collection ::= block_sequence | block_mapping
+ * flow_collection ::= flow_sequence | flow_mapping
+ * </pre>
+ */
+
+ private class ParseBlockNode implements Production {
+
+ public Event produce() {
+ return parseNode(true, false);
+ }
+ }
+
+ private Event parseFlowNode() {
+ return parseNode(false, false);
+ }
+
+ private Event parseBlockNodeOrIndentlessSequence() {
+ return parseNode(true, true);
+ }
+
+ private Event parseNode(boolean block, boolean indentlessSequence) {
+ Event event;
+ Mark startMark = null;
+ Mark endMark = null;
+ Mark tagMark = null;
+ if (scanner.checkToken(Token.ID.Alias)) {
+ AliasToken token = (AliasToken) scanner.getToken();
+ event = new AliasEvent(token.getValue(), token.getStartMark(), token.getEndMark());
+ state = states.pop();
+ } else {
+ String anchor = null;
+ TagTuple tagTokenTag = null;
+ if (scanner.checkToken(Token.ID.Anchor)) {
+ AnchorToken token = (AnchorToken) scanner.getToken();
+ startMark = token.getStartMark();
+ endMark = token.getEndMark();
+ anchor = token.getValue();
+ if (scanner.checkToken(Token.ID.Tag)) {
+ TagToken tagToken = (TagToken) scanner.getToken();
+ tagMark = tagToken.getStartMark();
+ endMark = tagToken.getEndMark();
+ tagTokenTag = tagToken.getValue();
}
- }
-
- private class ParseBlockMappingKey implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.Key)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
- states.push(new ParseBlockMappingValue());
- return parseBlockNodeOrIndentlessSequence();
- } else {
- state = new ParseBlockMappingValue();
- return processEmptyScalar(token.getEndMark());
- }
- }
- if (!scanner.checkToken(Token.ID.BlockEnd)) {
- Token token = scanner.peekToken();
- throw new ParserException("while parsing a block mapping", marks.pop(),
- "expected <block end>, but found " + token.getTokenId(),
- token.getStartMark());
- }
- Token token = scanner.getToken();
- Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
- state = states.pop();
- marks.pop();
- return event;
+ } else if (scanner.checkToken(Token.ID.Tag)) {
+ TagToken tagToken = (TagToken) scanner.getToken();
+ startMark = tagToken.getStartMark();
+ tagMark = startMark;
+ endMark = tagToken.getEndMark();
+ tagTokenTag = tagToken.getValue();
+ if (scanner.checkToken(Token.ID.Anchor)) {
+ AnchorToken token = (AnchorToken) scanner.getToken();
+ endMark = token.getEndMark();
+ anchor = token.getValue();
}
- }
-
- private class ParseBlockMappingValue implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.Value)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
- states.push(new ParseBlockMappingKey());
- return parseBlockNodeOrIndentlessSequence();
- } else {
- state = new ParseBlockMappingKey();
- return processEmptyScalar(token.getEndMark());
- }
- }
- state = new ParseBlockMappingKey();
- Token token = scanner.peekToken();
- return processEmptyScalar(token.getStartMark());
+ }
+ String tag = null;
+ if (tagTokenTag != null) {
+ String handle = tagTokenTag.getHandle();
+ String suffix = tagTokenTag.getSuffix();
+ if (handle != null) {
+ if (!directives.getTags().containsKey(handle)) {
+ throw new ParserException("while parsing a node", startMark,
+ "found undefined tag handle " + handle, tagMark);
+ }
+ tag = directives.getTags().get(handle) + suffix;
+ } else {
+ tag = suffix;
}
- }
-
- /**
- * <pre>
- * flow_sequence ::= FLOW-SEQUENCE-START
- * (flow_sequence_entry FLOW-ENTRY)*
- * flow_sequence_entry?
- * FLOW-SEQUENCE-END
- * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
- * Note that while production rules for both flow_sequence_entry and
- * flow_mapping_entry are equal, their interpretations are different.
- * For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
- * generate an inline mapping (set syntax).
- * </pre>
- */
- private class ParseFlowSequenceFirstEntry implements Production {
- public Event produce() {
- Token token = scanner.getToken();
- marks.push(token.getStartMark());
- return new ParseFlowSequenceEntry(true).produce();
+ }
+ if (startMark == null) {
+ startMark = scanner.peekToken().getStartMark();
+ endMark = startMark;
+ }
+ event = null;
+ boolean implicit = tag == null || tag.equals("!");
+ if (indentlessSequence && scanner.checkToken(Token.ID.BlockEntry)) {
+ endMark = scanner.peekToken().getEndMark();
+ event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
+ DumperOptions.FlowStyle.BLOCK);
+ state = new ParseIndentlessSequenceEntryKey();
+ } else {
+ if (scanner.checkToken(Token.ID.Scalar)) {
+ ScalarToken token = (ScalarToken) scanner.getToken();
+ endMark = token.getEndMark();
+ ImplicitTuple implicitValues;
+ if ((token.getPlain() && tag == null) || "!".equals(tag)) {
+ implicitValues = new ImplicitTuple(true, false);
+ } else if (tag == null) {
+ implicitValues = new ImplicitTuple(false, true);
+ } else {
+ implicitValues = new ImplicitTuple(false, false);
+ }
+ event = new ScalarEvent(anchor, tag, implicitValues, token.getValue(), startMark, endMark,
+ token.getStyle());
+ state = states.pop();
+ } else if (scanner.checkToken(Token.ID.FlowSequenceStart)) {
+ endMark = scanner.peekToken().getEndMark();
+ event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
+ DumperOptions.FlowStyle.FLOW);
+ state = new ParseFlowSequenceFirstEntry();
+ } else if (scanner.checkToken(Token.ID.FlowMappingStart)) {
+ endMark = scanner.peekToken().getEndMark();
+ event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
+ DumperOptions.FlowStyle.FLOW);
+ state = new ParseFlowMappingFirstKey();
+ } else if (block && scanner.checkToken(Token.ID.BlockSequenceStart)) {
+ endMark = scanner.peekToken().getStartMark();
+ event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
+ DumperOptions.FlowStyle.BLOCK);
+ state = new ParseBlockSequenceFirstEntry();
+ } else if (block && scanner.checkToken(Token.ID.BlockMappingStart)) {
+ endMark = scanner.peekToken().getStartMark();
+ event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
+ DumperOptions.FlowStyle.BLOCK);
+ state = new ParseBlockMappingFirstKey();
+ } else if (anchor != null || tag != null) {
+ // Empty scalars are allowed even if a tag or an anchor is
+ // specified.
+ event = new ScalarEvent(anchor, tag, new ImplicitTuple(implicit, false), "", startMark,
+ endMark, DumperOptions.ScalarStyle.PLAIN);
+ state = states.pop();
+ } else {
+ Token token = scanner.peekToken();
+ throw new ParserException("while parsing a " + (block ? "block" : "flow") + " node",
+ startMark, "expected the node content, but found '" + token.getTokenId() + "'",
+ token.getStartMark());
}
- }
+ }
+ }
+ return event;
+ }
+
+ // block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
+ // BLOCK-END
+
+ private class ParseBlockSequenceFirstEntry implements Production {
+
+ public Event produce() {
+ Token token = scanner.getToken();
+ marks.push(token.getStartMark());
+ return new ParseBlockSequenceEntryKey().produce();
+ }
+ }
+
+ private class ParseBlockSequenceEntryKey implements Production {
- private class ParseFlowSequenceEntry implements Production {
- private boolean first = false;
-
- public ParseFlowSequenceEntry(boolean first) {
- this.first = first;
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseBlockSequenceEntryKey();
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (scanner.checkToken(Token.ID.BlockEntry)) {
+ BlockEntryToken token = (BlockEntryToken) scanner.getToken();
+ return new ParseBlockSequenceEntryValue(token).produce();
+ }
+ if (!scanner.checkToken(Token.ID.BlockEnd)) {
+ Token token = scanner.peekToken();
+ throw new ParserException("while parsing a block collection", marks.pop(),
+ "expected <block end>, but found '" + token.getTokenId() + "'", token.getStartMark());
+ }
+ Token token = scanner.getToken();
+ Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
+ state = states.pop();
+ marks.pop();
+ return event;
+ }
+ }
+
+ private class ParseBlockSequenceEntryValue implements Production {
+
+ BlockEntryToken token;
+
+ public ParseBlockSequenceEntryValue(final BlockEntryToken token) {
+ this.token = token;
+ }
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseBlockSequenceEntryValue(token);
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.BlockEnd)) {
+ states.push(new ParseBlockSequenceEntryKey());
+ return new ParseBlockNode().produce();
+ } else {
+ state = new ParseBlockSequenceEntryKey();
+ return processEmptyScalar(token.getEndMark());
+ }
+ }
+ }
+
+ // indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+
+ private class ParseIndentlessSequenceEntryKey implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseIndentlessSequenceEntryKey();
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (scanner.checkToken(Token.ID.BlockEntry)) {
+ BlockEntryToken token = (BlockEntryToken) scanner.getToken();
+ return new ParseIndentlessSequenceEntryValue(token).produce();
+ }
+ Token token = scanner.peekToken();
+ Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
+ state = states.pop();
+ return event;
+ }
+ }
+
+ private class ParseIndentlessSequenceEntryValue implements Production {
+
+ BlockEntryToken token;
+
+ public ParseIndentlessSequenceEntryValue(final BlockEntryToken token) {
+ this.token = token;
+ }
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseIndentlessSequenceEntryValue(token);
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.Key, Token.ID.Value,
+ Token.ID.BlockEnd)) {
+ states.push(new ParseIndentlessSequenceEntryKey());
+ return new ParseBlockNode().produce();
+ } else {
+ state = new ParseIndentlessSequenceEntryKey();
+ return processEmptyScalar(token.getEndMark());
+ }
+ }
+ }
+
+ private class ParseBlockMappingFirstKey implements Production {
+
+ public Event produce() {
+ Token token = scanner.getToken();
+ marks.push(token.getStartMark());
+ return new ParseBlockMappingKey().produce();
+ }
+ }
+
+ private class ParseBlockMappingKey implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseBlockMappingKey();
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (scanner.checkToken(Token.ID.Key)) {
+ Token token = scanner.getToken();
+ if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
+ states.push(new ParseBlockMappingValue());
+ return parseBlockNodeOrIndentlessSequence();
+ } else {
+ state = new ParseBlockMappingValue();
+ return processEmptyScalar(token.getEndMark());
}
-
- public Event produce() {
- if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
- if (!first) {
- if (scanner.checkToken(Token.ID.FlowEntry)) {
- scanner.getToken();
- } else {
- Token token = scanner.peekToken();
- throw new ParserException("while parsing a flow sequence", marks.pop(),
- "expected ',' or ']', but got " + token.getTokenId(),
- token.getStartMark());
- }
- }
- if (scanner.checkToken(Token.ID.Key)) {
- Token token = scanner.peekToken();
- Event event = new MappingStartEvent(null, null, true, token.getStartMark(),
- token.getEndMark(), Boolean.TRUE);
- state = new ParseFlowSequenceEntryMappingKey();
- return event;
- } else if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
- states.push(new ParseFlowSequenceEntry(false));
- return parseFlowNode();
- }
- }
- Token token = scanner.getToken();
- Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
- state = states.pop();
- marks.pop();
- return event;
+ }
+ if (!scanner.checkToken(Token.ID.BlockEnd)) {
+ Token token = scanner.peekToken();
+ throw new ParserException("while parsing a block mapping", marks.pop(),
+ "expected <block end>, but found '" + token.getTokenId() + "'", token.getStartMark());
+ }
+ Token token = scanner.getToken();
+ Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
+ state = states.pop();
+ marks.pop();
+ return event;
+ }
+ }
+
+ private class ParseBlockMappingValue implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Value)) {
+ Token token = scanner.getToken();
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseBlockMappingValueComment();
+ return state.produce();
+ } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
+ states.push(new ParseBlockMappingKey());
+ return parseBlockNodeOrIndentlessSequence();
+ } else {
+ state = new ParseBlockMappingKey();
+ return processEmptyScalar(token.getEndMark());
}
- }
-
- private class ParseFlowSequenceEntryMappingKey implements Production {
- public Event produce() {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
- states.push(new ParseFlowSequenceEntryMappingValue());
- return parseFlowNode();
- } else {
- state = new ParseFlowSequenceEntryMappingValue();
- return processEmptyScalar(token.getEndMark());
- }
+ } else if (scanner.checkToken(Token.ID.Scalar)) {
+ states.push(new ParseBlockMappingKey());
+ return parseBlockNodeOrIndentlessSequence();
+ }
+ state = new ParseBlockMappingKey();
+ Token token = scanner.peekToken();
+ return processEmptyScalar(token.getStartMark());
+ }
+ }
+
+ private class ParseBlockMappingValueComment implements Production {
+
+ List<CommentToken> tokens = new LinkedList<>();
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ tokens.add((CommentToken) scanner.getToken());
+ return produce();
+ } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
+ if (!tokens.isEmpty()) {
+ return produceCommentEvent(tokens.remove(0));
}
- }
-
- private class ParseFlowSequenceEntryMappingValue implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.Value)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
- states.push(new ParseFlowSequenceEntryMappingEnd());
- return parseFlowNode();
- } else {
- state = new ParseFlowSequenceEntryMappingEnd();
- return processEmptyScalar(token.getEndMark());
- }
- } else {
- state = new ParseFlowSequenceEntryMappingEnd();
- Token token = scanner.peekToken();
- return processEmptyScalar(token.getStartMark());
+ states.push(new ParseBlockMappingKey());
+ return parseBlockNodeOrIndentlessSequence();
+ } else {
+ state = new ParseBlockMappingValueCommentList(tokens);
+ return processEmptyScalar(scanner.peekToken().getStartMark());
+ }
+ }
+ }
+
+ private class ParseBlockMappingValueCommentList implements Production {
+
+ List<CommentToken> tokens;
+
+ public ParseBlockMappingValueCommentList(final List<CommentToken> tokens) {
+ this.tokens = tokens;
+ }
+
+ public Event produce() {
+ if (!tokens.isEmpty()) {
+ return produceCommentEvent(tokens.remove(0));
+ }
+ return new ParseBlockMappingKey().produce();
+ }
+ }
+
+ /**
+ * <pre>
+ * flow_sequence ::= FLOW-SEQUENCE-START
+ * (flow_sequence_entry FLOW-ENTRY)*
+ * flow_sequence_entry?
+ * FLOW-SEQUENCE-END
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ * Note that while production rules for both flow_sequence_entry and
+ * flow_mapping_entry are equal, their interpretations are different.
+ * For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
+ * generate an inline mapping (set syntax).
+ * </pre>
+ */
+ private class ParseFlowSequenceFirstEntry implements Production {
+
+ public Event produce() {
+ Token token = scanner.getToken();
+ marks.push(token.getStartMark());
+ return new ParseFlowSequenceEntry(true).produce();
+ }
+ }
+
+ private class ParseFlowSequenceEntry implements Production {
+
+ private final boolean first;
+
+ public ParseFlowSequenceEntry(boolean first) {
+ this.first = first;
+ }
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseFlowSequenceEntry(first);
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
+ if (!first) {
+ if (scanner.checkToken(Token.ID.FlowEntry)) {
+ scanner.getToken();
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseFlowSequenceEntry(true);
+ return produceCommentEvent((CommentToken) scanner.getToken());
}
- }
- }
-
- private class ParseFlowSequenceEntryMappingEnd implements Production {
- public Event produce() {
- state = new ParseFlowSequenceEntry(false);
+ } else {
Token token = scanner.peekToken();
- return new MappingEndEvent(token.getStartMark(), token.getEndMark());
+ throw new ParserException("while parsing a flow sequence", marks.pop(),
+ "expected ',' or ']', but got " + token.getTokenId(), token.getStartMark());
+ }
}
- }
-
- /**
- * <pre>
- * flow_mapping ::= FLOW-MAPPING-START
- * (flow_mapping_entry FLOW-ENTRY)*
- * flow_mapping_entry?
- * FLOW-MAPPING-END
- * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
- * </pre>
- */
- private class ParseFlowMappingFirstKey implements Production {
- public Event produce() {
- Token token = scanner.getToken();
- marks.push(token.getStartMark());
- return new ParseFlowMappingKey(true).produce();
+ if (scanner.checkToken(Token.ID.Key)) {
+ Token token = scanner.peekToken();
+ Event event = new MappingStartEvent(null, null, true, token.getStartMark(),
+ token.getEndMark(), DumperOptions.FlowStyle.FLOW);
+ state = new ParseFlowSequenceEntryMappingKey();
+ return event;
+ } else if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
+ states.push(new ParseFlowSequenceEntry(false));
+ return parseFlowNode();
}
- }
-
- private class ParseFlowMappingKey implements Production {
- private boolean first = false;
-
- public ParseFlowMappingKey(boolean first) {
- this.first = first;
+ }
+ Token token = scanner.getToken();
+ Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
+ if (!scanner.checkToken(Token.ID.Comment)) {
+ state = states.pop();
+ } else {
+ state = new ParseFlowEndComment();
+ }
+ marks.pop();
+ return event;
+ }
+ }
+
+ private class ParseFlowEndComment implements Production {
+
+ public Event produce() {
+ Event event = produceCommentEvent((CommentToken) scanner.getToken());
+ if (!scanner.checkToken(Token.ID.Comment)) {
+ state = states.pop();
+ }
+ return event;
+ }
+ }
+
+ private class ParseFlowSequenceEntryMappingKey implements Production {
+
+ public Event produce() {
+ Token token = scanner.getToken();
+ if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
+ states.push(new ParseFlowSequenceEntryMappingValue());
+ return parseFlowNode();
+ } else {
+ state = new ParseFlowSequenceEntryMappingValue();
+ return processEmptyScalar(token.getEndMark());
+ }
+ }
+ }
+
+ private class ParseFlowSequenceEntryMappingValue implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Value)) {
+ Token token = scanner.getToken();
+ if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
+ states.push(new ParseFlowSequenceEntryMappingEnd());
+ return parseFlowNode();
+ } else {
+ state = new ParseFlowSequenceEntryMappingEnd();
+ return processEmptyScalar(token.getEndMark());
}
-
- public Event produce() {
- if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
- if (!first) {
- if (scanner.checkToken(Token.ID.FlowEntry)) {
- scanner.getToken();
- } else {
- Token token = scanner.peekToken();
- throw new ParserException("while parsing a flow mapping", marks.pop(),
- "expected ',' or '}', but got " + token.getTokenId(),
- token.getStartMark());
- }
- }
- if (scanner.checkToken(Token.ID.Key)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry,
- Token.ID.FlowMappingEnd)) {
- states.push(new ParseFlowMappingValue());
- return parseFlowNode();
- } else {
- state = new ParseFlowMappingValue();
- return processEmptyScalar(token.getEndMark());
- }
- } else if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
- states.push(new ParseFlowMappingEmptyValue());
- return parseFlowNode();
- }
+ } else {
+ state = new ParseFlowSequenceEntryMappingEnd();
+ Token token = scanner.peekToken();
+ return processEmptyScalar(token.getStartMark());
+ }
+ }
+ }
+
+ private class ParseFlowSequenceEntryMappingEnd implements Production {
+
+ public Event produce() {
+ state = new ParseFlowSequenceEntry(false);
+ Token token = scanner.peekToken();
+ return new MappingEndEvent(token.getStartMark(), token.getEndMark());
+ }
+ }
+
+ /**
+ * <pre>
+ * flow_mapping ::= FLOW-MAPPING-START
+ * (flow_mapping_entry FLOW-ENTRY)*
+ * flow_mapping_entry?
+ * FLOW-MAPPING-END
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ * </pre>
+ */
+ private class ParseFlowMappingFirstKey implements Production {
+
+ public Event produce() {
+ Token token = scanner.getToken();
+ marks.push(token.getStartMark());
+ return new ParseFlowMappingKey(true).produce();
+ }
+ }
+
+ private class ParseFlowMappingKey implements Production {
+
+ private final boolean first;
+
+ public ParseFlowMappingKey(boolean first) {
+ this.first = first;
+ }
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseFlowMappingKey(first);
+ return produceCommentEvent((CommentToken) scanner.getToken());
+ }
+ if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
+ if (!first) {
+ if (scanner.checkToken(Token.ID.FlowEntry)) {
+ scanner.getToken();
+ if (scanner.checkToken(Token.ID.Comment)) {
+ state = new ParseFlowMappingKey(true);
+ return produceCommentEvent((CommentToken) scanner.getToken());
}
- Token token = scanner.getToken();
- Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
- state = states.pop();
- marks.pop();
- return event;
+ } else {
+ Token token = scanner.peekToken();
+ throw new ParserException("while parsing a flow mapping", marks.pop(),
+ "expected ',' or '}', but got " + token.getTokenId(), token.getStartMark());
+ }
}
- }
-
- private class ParseFlowMappingValue implements Production {
- public Event produce() {
- if (scanner.checkToken(Token.ID.Value)) {
- Token token = scanner.getToken();
- if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) {
- states.push(new ParseFlowMappingKey(false));
- return parseFlowNode();
- } else {
- state = new ParseFlowMappingKey(false);
- return processEmptyScalar(token.getEndMark());
- }
- } else {
- state = new ParseFlowMappingKey(false);
- Token token = scanner.peekToken();
- return processEmptyScalar(token.getStartMark());
- }
+ if (scanner.checkToken(Token.ID.Key)) {
+ Token token = scanner.getToken();
+ if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) {
+ states.push(new ParseFlowMappingValue());
+ return parseFlowNode();
+ } else {
+ state = new ParseFlowMappingValue();
+ return processEmptyScalar(token.getEndMark());
+ }
+ } else if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
+ states.push(new ParseFlowMappingEmptyValue());
+ return parseFlowNode();
}
- }
-
- private class ParseFlowMappingEmptyValue implements Production {
- public Event produce() {
- state = new ParseFlowMappingKey(false);
- return processEmptyScalar(scanner.peekToken().getStartMark());
+ }
+ Token token = scanner.getToken();
+ Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
+ marks.pop();
+ if (!scanner.checkToken(Token.ID.Comment)) {
+ state = states.pop();
+ } else {
+ state = new ParseFlowEndComment();
+ }
+ return event;
+ }
+ }
+
+ private class ParseFlowMappingValue implements Production {
+
+ public Event produce() {
+ if (scanner.checkToken(Token.ID.Value)) {
+ Token token = scanner.getToken();
+ if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) {
+ states.push(new ParseFlowMappingKey(false));
+ return parseFlowNode();
+ } else {
+ state = new ParseFlowMappingKey(false);
+ return processEmptyScalar(token.getEndMark());
}
- }
-
- /**
- * <pre>
- * block_mapping ::= BLOCK-MAPPING_START
- * ((KEY block_node_or_indentless_sequence?)?
- * (VALUE block_node_or_indentless_sequence?)?)*
- * BLOCK-END
- * </pre>
- */
- private Event processEmptyScalar(Mark mark) {
- return new ScalarEvent(null, null, new ImplicitTuple(true, false), "", mark, mark, (char) 0);
- }
+ } else {
+ state = new ParseFlowMappingKey(false);
+ Token token = scanner.peekToken();
+ return processEmptyScalar(token.getStartMark());
+ }
+ }
+ }
+
+ private class ParseFlowMappingEmptyValue implements Production {
+
+ public Event produce() {
+ state = new ParseFlowMappingKey(false);
+ return processEmptyScalar(scanner.peekToken().getStartMark());
+ }
+ }
+
+ /**
+ * <pre>
+ * block_mapping ::= BLOCK-MAPPING_START
+ * ((KEY block_node_or_indentless_sequence?)?
+ * (VALUE block_node_or_indentless_sequence?)?)*
+ * BLOCK-END
+ * </pre>
+ */
+ private Event processEmptyScalar(Mark mark) {
+ return new ScalarEvent(null, null, new ImplicitTuple(true, false), "", mark, mark,
+ DumperOptions.ScalarStyle.PLAIN);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/parser/Production.java b/src/main/java/org/yaml/snakeyaml/parser/Production.java
index 5dd3949c..d886733c 100644
--- a/src/main/java/org/yaml/snakeyaml/parser/Production.java
+++ b/src/main/java/org/yaml/snakeyaml/parser/Production.java
@@ -1,28 +1,27 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.parser;
import org.yaml.snakeyaml.events.Event;
/**
- * Helper for {@link ParserImpl}. A grammar rule to apply given the symbols on
- * top of its stack and the next input token
- *
- * @see <a href="http://en.wikipedia.org/wiki/LL_parser"></a>
+ * Helper for {@link ParserImpl}. A grammar rule to apply given the symbols on top of its stack and
+ * the next input token
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/LL_parser">LL parser</a>
*/
interface Production {
- Event produce();
+
+ Event produce();
}
diff --git a/src/main/java/org/yaml/snakeyaml/parser/VersionTagsTuple.java b/src/main/java/org/yaml/snakeyaml/parser/VersionTagsTuple.java
index 44ed2fbb..6a2a1f64 100644
--- a/src/main/java/org/yaml/snakeyaml/parser/VersionTagsTuple.java
+++ b/src/main/java/org/yaml/snakeyaml/parser/VersionTagsTuple.java
@@ -1,46 +1,44 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.parser;
import java.util.Map;
-
import org.yaml.snakeyaml.DumperOptions.Version;
/**
* Store the internal state for directives
*/
class VersionTagsTuple {
- private Version version;
- private Map<String, String> tags;
- public VersionTagsTuple(Version version, Map<String, String> tags) {
- this.version = version;
- this.tags = tags;
- }
+ private final Version version;
+ private final Map<String, String> tags;
+
+ public VersionTagsTuple(Version version, Map<String, String> tags) {
+ this.version = version;
+ this.tags = tags;
+ }
- public Version getVersion() {
- return version;
- }
+ public Version getVersion() {
+ return version;
+ }
- public Map<String, String> getTags() {
- return tags;
- }
+ public Map<String, String> getTags() {
+ return tags;
+ }
- @Override
- public String toString() {
- return String.format("VersionTagsTuple<%s, %s>", version, tags);
- }
+ @Override
+ public String toString() {
+ return String.format("VersionTagsTuple<%s, %s>", version, tags);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/reader/ReaderException.java b/src/main/java/org/yaml/snakeyaml/reader/ReaderException.java
index 7a8a06b2..d44141ec 100644
--- a/src/main/java/org/yaml/snakeyaml/reader/ReaderException.java
+++ b/src/main/java/org/yaml/snakeyaml/reader/ReaderException.java
@@ -1,51 +1,50 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.reader;
import org.yaml.snakeyaml.error.YAMLException;
public class ReaderException extends YAMLException {
- private static final long serialVersionUID = 8710781187529689083L;
- private final String name;
- private final char character;
- private final int position;
-
- public ReaderException(String name, int position, char character, String message) {
- super(message);
- this.name = name;
- this.character = character;
- this.position = position;
- }
-
- public String getName() {
- return name;
- }
-
- public char getCharacter() {
- return character;
- }
-
- public int getPosition() {
- return position;
- }
-
- @Override
- public String toString() {
- return "unacceptable character '" + character + "' (0x"
- + Integer.toHexString((int) character).toUpperCase() + ") " + getMessage()
- + "\nin \"" + name + "\", position " + position;
- }
+
+ private static final long serialVersionUID = 8710781187529689083L;
+ private final String name;
+ private final int codePoint;
+ private final int position;
+
+ public ReaderException(String name, int position, int codePoint, String message) {
+ super(message);
+ this.name = name;
+ this.codePoint = codePoint;
+ this.position = position;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getCodePoint() {
+ return codePoint;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ @Override
+ public String toString() {
+ final String s = new String(Character.toChars(codePoint));
+ return "unacceptable code point '" + s + "' (0x" + Integer.toHexString(codePoint).toUpperCase()
+ + ") " + getMessage() + "\nin \"" + name + "\", position " + position;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/reader/StreamReader.java b/src/main/java/org/yaml/snakeyaml/reader/StreamReader.java
index 56ec0078..6799e76e 100644
--- a/src/main/java/org/yaml/snakeyaml/reader/StreamReader.java
+++ b/src/main/java/org/yaml/snakeyaml/reader/StreamReader.java
@@ -1,220 +1,230 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.reader;
import java.io.IOException;
import java.io.Reader;
-import java.nio.charset.Charset;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
+import java.io.StringReader;
+import java.util.Arrays;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.scanner.Constant;
/**
- * Reader: checks if characters are in allowed range, adds '\0' to the end.
+ * Reader: checks if code points are in allowed range. Returns '\0' when end of data has been
+ * reached.
*/
public class StreamReader {
- public final static Pattern NON_PRINTABLE = Pattern
- .compile("[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD]");
- private String name;
- private final Reader stream;
- private int pointer = 0;
- private boolean eof = true;
- private String buffer;
- private int index = 0;
- private int line = 0;
- private int column = 0;
- private char[] data;
-
- public StreamReader(String stream) {
- this.name = "'string'";
- this.buffer = ""; // to set length to 0
- checkPrintable(stream);
- this.buffer = stream + "\0";
- this.stream = null;
- this.eof = true;
- this.data = null;
- }
-
- public StreamReader(Reader reader) {
- this.name = "'reader'";
- this.buffer = "";
- this.stream = reader;
- this.eof = false;
- this.data = new char[1024];
- this.update();
- }
- void checkPrintable(CharSequence data) {
- Matcher em = NON_PRINTABLE.matcher(data);
- if (em.find()) {
- int position = this.index + this.buffer.length() - this.pointer + em.start();
- throw new ReaderException(name, position, em.group().charAt(0),
- "special characters are not allowed");
+ private String name;
+ private final Reader stream;
+ /**
+ * Read data (as a moving window for input stream)
+ */
+ private int[] dataWindow;
+
+ /**
+ * Real length of the data in dataWindow
+ */
+ private int dataLength;
+
+ /**
+ * The variable points to the current position in the data array
+ */
+ private int pointer = 0;
+ private boolean eof;
+ private int index = 0; // in code points
+ private int line = 0;
+ private int column = 0; // in code points
+ private final char[] buffer; // temp buffer for one read operation (to avoid
+ // creating the array in stack)
+
+ private static final int BUFFER_SIZE = 1025;
+
+ public StreamReader(String stream) {
+ this(new StringReader(stream));
+ this.name = "'string'";
+ }
+
+ public StreamReader(Reader reader) {
+ this.name = "'reader'";
+ this.dataWindow = new int[0];
+ this.dataLength = 0;
+ this.stream = reader;
+ this.eof = false;
+ this.buffer = new char[BUFFER_SIZE];
+ }
+
+ public static boolean isPrintable(final String data) {
+ final int length = data.length();
+ for (int offset = 0; offset < length;) {
+ final int codePoint = data.codePointAt(offset);
+
+ if (!isPrintable(codePoint)) {
+ return false;
+ }
+
+ offset += Character.charCount(codePoint);
+ }
+
+ return true;
+ }
+
+ public static boolean isPrintable(final int c) {
+ return (c >= 0x20 && c <= 0x7E) || c == 0x9 || c == 0xA || c == 0xD || c == 0x85
+ || (c >= 0xA0 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD)
+ || (c >= 0x10000 && c <= 0x10FFFF);
+ }
+
+ public Mark getMark() {
+ return new Mark(name, this.index, this.line, this.column, this.dataWindow, this.pointer);
+ }
+
+ public void forward() {
+ forward(1);
+ }
+
+ /**
+ * read the next length characters and move the pointer. if the last character is high surrogate
+ * one more character will be read
+ *
+ * @param length amount of characters to move forward
+ */
+ public void forward(int length) {
+ for (int i = 0; i < length && ensureEnoughData(); i++) {
+ int c = dataWindow[pointer++];
+ this.index++;
+ if (Constant.LINEBR.has(c)
+ || (c == '\r' && (ensureEnoughData() && dataWindow[pointer] != '\n'))) {
+ this.line++;
+ this.column = 0;
+ } else if (c != 0xFEFF) {
+ this.column++;
+ }
+ }
+ }
+
+ public int peek() {
+ return (ensureEnoughData()) ? dataWindow[pointer] : '\0';
+ }
+
+ /**
+ * Peek the next index-th code point
+ *
+ * @param index to peek
+ * @return the next index-th code point
+ */
+ public int peek(int index) {
+ return (ensureEnoughData(index)) ? dataWindow[pointer + index] : '\0';
+ }
+
+ /**
+ * peek the next length code points
+ *
+ * @param length amount of the characters to peek
+ * @return the next length code points
+ */
+ public String prefix(int length) {
+ if (length == 0) {
+ return "";
+ } else if (ensureEnoughData(length)) {
+ return new String(this.dataWindow, pointer, length);
+ } else {
+ return new String(this.dataWindow, pointer, Math.min(length, dataLength - pointer));
+ }
+ }
+
+ /**
+ * prefix(length) immediately followed by forward(length)
+ *
+ * @param length amount of characters to get
+ * @return the next length code points
+ */
+ public String prefixForward(int length) {
+ final String prefix = prefix(length);
+ this.pointer += length;
+ this.index += length;
+ // prefix never contains new line characters
+ this.column += length;
+ return prefix;
+ }
+
+ private boolean ensureEnoughData() {
+ return ensureEnoughData(0);
+ }
+
+ private boolean ensureEnoughData(int size) {
+ if (!eof && pointer + size >= dataLength) {
+ update();
+ }
+ return (this.pointer + size) < dataLength;
+ }
+
+ private void update() {
+ try {
+ int read = stream.read(buffer, 0, BUFFER_SIZE - 1);
+ if (read > 0) {
+ int cpIndex = (dataLength - pointer);
+ dataWindow = Arrays.copyOfRange(dataWindow, pointer, dataLength + read);
+
+ if (Character.isHighSurrogate(buffer[read - 1])) {
+ if (stream.read(buffer, read, 1) == -1) {
+ eof = true;
+ } else {
+ read++;
+ }
}
- }
- /**
- * Checks <code>chars</chars> for the non-printable characters.
- *
- * @param chars
- * the array where to search.
- * @param begin
- * the beginning index, inclusive.
- * @param end
- * the ending index, exclusive.
- * @throws ReaderException
- * if <code>chars</code> contains non-printable character(s).
- */
- void checkPrintable(final char[] chars, final int begin, final int end) {
- for (int i = begin; i < end; i++) {
- final char c = chars[i];
-
- if (isPrintable(c)) {
- continue;
- }
-
- int position = this.index + this.buffer.length() - this.pointer + i;
- throw new ReaderException(name, position, c, "special characters are not allowed");
+ int nonPrintable = ' ';
+ for (int i = 0; i < read; cpIndex++) {
+ int codePoint = Character.codePointAt(buffer, i);
+ dataWindow[cpIndex] = codePoint;
+ if (isPrintable(codePoint)) {
+ i += Character.charCount(codePoint);
+ } else {
+ nonPrintable = codePoint;
+ i = read;
+ }
}
- }
-
- public static boolean isPrintable(final char c) {
- return (c >= '\u0020' && c <= '\u007E') || c == '\n' || c == '\r' || c == '\t'
- || c == '\u0085' || (c >= '\u00A0' && c <= '\uD7FF')
- || (c >= '\uE000' && c <= '\uFFFD');
- }
- public Mark getMark() {
- return new Mark(name, this.index, this.line, this.column, this.buffer, this.pointer);
- }
-
- public void forward() {
- forward(1);
- }
-
- /**
- * read the next length characters and move the pointer.
- *
- * @param length
- */
- public void forward(int length) {
- if (this.pointer + length + 1 >= this.buffer.length()) {
- update();
- }
- char ch = 0;
- for (int i = 0; i < length; i++) {
- ch = this.buffer.charAt(this.pointer);
- this.pointer++;
- this.index++;
- if (Constant.LINEBR.has(ch) || (ch == '\r' && buffer.charAt(pointer) != '\n')) {
- this.line++;
- this.column = 0;
- } else if (ch != '\uFEFF') {
- this.column++;
- }
+ dataLength = cpIndex;
+ pointer = 0;
+ if (nonPrintable != ' ') {
+ throw new ReaderException(name, cpIndex - 1, nonPrintable,
+ "special characters are not allowed");
}
+ } else {
+ eof = true;
+ }
+ } catch (IOException ioe) {
+ throw new YAMLException(ioe);
}
+ }
- public char peek() {
- return this.buffer.charAt(this.pointer);
- }
- /**
- * Peek the next index-th character
- *
- * @param index
- * @return the next index-th character
- */
- public char peek(int index) {
- if (this.pointer + index + 1 > this.buffer.length()) {
- update();
- }
- return this.buffer.charAt(this.pointer + index);
- }
+ public int getColumn() {
+ return column;
+ }
- /**
- * peek the next length characters
- *
- * @param length
- * @return the next length characters
- */
- public String prefix(int length) {
- if (this.pointer + length >= this.buffer.length()) {
- update();
- }
- if (this.pointer + length > this.buffer.length()) {
- return this.buffer.substring(this.pointer);
- }
- return this.buffer.substring(this.pointer, this.pointer + length);
- }
-
- /**
- * prefix(length) immediately followed by forward(length)
- */
- public String prefixForward(int length) {
- final String prefix = prefix(length);
- this.pointer += length;
- this.index += length;
- // prefix never contains new line characters
- this.column += length;
- return prefix;
- }
-
- private void update() {
- if (!this.eof) {
- this.buffer = buffer.substring(this.pointer);
- this.pointer = 0;
- try {
- int converted = this.stream.read(data);
- if (converted > 0) {
- /*
- * Let's create StringBuilder manually. Anyway str1 + str2
- * generates new StringBuilder(str1).append(str2).toSting()
- * Giving correct capacity to the constructor prevents
- * unnecessary operations in appends.
- */
- checkPrintable(data, 0, converted);
- this.buffer = new StringBuilder(buffer.length() + converted).append(buffer)
- .append(data, 0, converted).toString();
- } else {
- this.eof = true;
- this.buffer += "\0";
- }
- } catch (IOException ioe) {
- throw new YAMLException(ioe);
- }
- }
- }
+ /**
+ * @return current position as number (in characters) from the beginning of the stream
+ */
+ public int getIndex() {
+ return index;
+ }
- public int getColumn() {
- return column;
- }
-
- public Charset getEncoding() {
- return Charset.forName(((UnicodeReader) this.stream).getEncoding());
- }
-
- public int getIndex() {
- return index;
- }
-
- public int getLine() {
- return line;
- }
+ public int getLine() {
+ return line;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/reader/UnicodeReader.java b/src/main/java/org/yaml/snakeyaml/reader/UnicodeReader.java
index dd9dc39b..4c9c9039 100644
--- a/src/main/java/org/yaml/snakeyaml/reader/UnicodeReader.java
+++ b/src/main/java/org/yaml/snakeyaml/reader/UnicodeReader.java
@@ -1,40 +1,29 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.reader;
/**
- version: 1.1 / 2007-01-25
- - changed BOM recognition ordering (longer boms first)
-
- Original pseudocode : Thomas Weidenfeller
- Implementation tweaked: Aki Nieminen
- Implementation changed: Andrey Somov
- * UTF-32 removed because it is not supported by YAML
- * no default encoding
-
- http://www.unicode.org/unicode/faq/utf_bom.html
- BOMs:
- 00 00 FE FF = UTF-32, big-endian
- FF FE 00 00 = UTF-32, little-endian
- EF BB BF = UTF-8,
- FE FF = UTF-16, big-endian
- FF FE = UTF-16, little-endian
-
- Win2k Notepad:
- Unicode format = UTF-16LE
+ * version: 1.1 / 2007-01-25 - changed BOM recognition ordering (longer boms first)
+ *
+ * Original pseudocode : Thomas Weidenfeller Implementation tweaked: Aki Nieminen Implementation
+ * changed: Andrey Somov UTF-32 removed because it is not supported by YAML no default encoding
+ *
+ * http://www.unicode.org/unicode/faq/utf_bom.html BOMs: 00 00 FE FF = UTF-32, big-endian FF FE 00
+ * 00 = UTF-32, little-endian EF BB BF = UTF-8, FE FF = UTF-16, big-endian FF FE = UTF-16,
+ * little-endian
+ *
+ * Win2k Notepad: Unicode format = UTF-16LE
***/
import java.io.IOException;
@@ -45,81 +34,87 @@ import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
/**
- * Generic unicode textreader, which will use BOM mark to identify the encoding
- * to be used. If BOM is not found then use a given default or system encoding.
+ * Generic unicode textreader, which will use BOM mark to identify the encoding to be used. If BOM
+ * is not found then use a given default or system encoding.
*/
public class UnicodeReader extends Reader {
- private static final Charset UTF8 = Charset.forName("UTF-8");
- private static final Charset UTF16BE = Charset.forName("UTF-16BE");
- private static final Charset UTF16LE = Charset.forName("UTF-16LE");
-
- PushbackInputStream internalIn;
- InputStreamReader internalIn2 = null;
-
- private static final int BOM_SIZE = 3;
- /**
- * @param in
- * InputStream to be read
- */
- public UnicodeReader(InputStream in) {
- internalIn = new PushbackInputStream(in, BOM_SIZE);
+ private static final Charset UTF8 = StandardCharsets.UTF_8;
+ private static final Charset UTF16BE = StandardCharsets.UTF_16BE;
+ private static final Charset UTF16LE = StandardCharsets.UTF_16LE;
+
+ PushbackInputStream internalIn;
+ InputStreamReader internalIn2 = null;
+
+ private static final int BOM_SIZE = 3;
+
+ /**
+ * @param in InputStream to be read
+ */
+ public UnicodeReader(InputStream in) {
+ internalIn = new PushbackInputStream(in, BOM_SIZE);
+ }
+
+ /**
+ * Get stream encoding or NULL if stream is uninitialized. Call init() or read() method to
+ * initialize it.
+ *
+ * @return the name of the character encoding being used by this stream.
+ */
+ public String getEncoding() {
+ return internalIn2.getEncoding();
+ }
+
+ /**
+ * Read-ahead four bytes and check for BOM marks. Extra bytes are unread back to the stream, only
+ * BOM bytes are skipped.
+ *
+ * @throws IOException if InputStream cannot be created
+ */
+ protected void init() throws IOException {
+ if (internalIn2 != null) {
+ return;
}
- /**
- * Get stream encoding or NULL if stream is uninitialized. Call init() or
- * read() method to initialize it.
- */
- public String getEncoding() {
- return internalIn2.getEncoding();
+ Charset encoding;
+ byte[] bom = new byte[BOM_SIZE];
+ int n, unread;
+ n = internalIn.read(bom, 0, bom.length);
+
+ if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
+ encoding = UTF8;
+ unread = n - 3;
+ } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
+ encoding = UTF16BE;
+ unread = n - 2;
+ } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
+ encoding = UTF16LE;
+ unread = n - 2;
+ } else {
+ // Unicode BOM mark not found, unread all bytes
+ encoding = UTF8;
+ unread = n;
}
- /**
- * Read-ahead four bytes and check for BOM marks. Extra bytes are unread
- * back to the stream, only BOM bytes are skipped.
- */
- protected void init() throws IOException {
- if (internalIn2 != null)
- return;
-
- Charset encoding;
- byte bom[] = new byte[BOM_SIZE];
- int n, unread;
- n = internalIn.read(bom, 0, bom.length);
-
- if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
- encoding = UTF8;
- unread = n - 3;
- } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
- encoding = UTF16BE;
- unread = n - 2;
- } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
- encoding = UTF16LE;
- unread = n - 2;
- } else {
- // Unicode BOM mark not found, unread all bytes
- encoding = UTF8;
- unread = n;
- }
-
- if (unread > 0)
- internalIn.unread(bom, (n - unread), unread);
-
- // Use given encoding
- CharsetDecoder decoder = encoding.newDecoder().onUnmappableCharacter(
- CodingErrorAction.REPORT);
- internalIn2 = new InputStreamReader(internalIn, decoder);
+ if (unread > 0) {
+ internalIn.unread(bom, (n - unread), unread);
}
- public void close() throws IOException {
- init();
- internalIn2.close();
- }
-
- public int read(char[] cbuf, int off, int len) throws IOException {
- init();
- return internalIn2.read(cbuf, off, len);
- }
-} \ No newline at end of file
+ // Use given encoding
+ CharsetDecoder decoder = encoding.newDecoder().onUnmappableCharacter(CodingErrorAction.REPORT);
+ internalIn2 = new InputStreamReader(internalIn, decoder);
+ }
+
+ public void close() throws IOException {
+ init();
+ internalIn2.close();
+ }
+
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ init();
+ return internalIn2.read(cbuf, off, len);
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java b/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java
index c4419fb2..6257b4b6 100644
--- a/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java
+++ b/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.representer;
@@ -21,7 +19,7 @@ import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.DumperOptions.ScalarStyle;
import org.yaml.snakeyaml.introspector.PropertyUtils;
@@ -37,166 +35,175 @@ import org.yaml.snakeyaml.nodes.Tag;
* Represent basic YAML structures: scalar, sequence, mapping
*/
public abstract class BaseRepresenter {
- protected final Map<Class<?>, Represent> representers = new HashMap<Class<?>, Represent>();
- /**
- * in Java 'null' is not a type. So we have to keep the null representer
- * separately otherwise it will coincide with the default representer which
- * is stored with the key null.
- */
- protected Represent nullRepresenter;
- // the order is important (map can be also a sequence of key-values)
- protected final Map<Class<?>, Represent> multiRepresenters = new LinkedHashMap<Class<?>, Represent>();
- protected Character defaultScalarStyle;
- protected FlowStyle defaultFlowStyle = FlowStyle.AUTO;
- protected final Map<Object, Node> representedObjects = new IdentityHashMap<Object, Node>() {
- private static final long serialVersionUID = -5576159264232131854L;
-
- public Node put(Object key, Node value) {
- return super.put(key, new AnchorNode(value));
- }
- };
- protected Object objectToRepresent;
- private PropertyUtils propertyUtils;
- private boolean explicitPropertyUtils = false;
-
- public Node represent(Object data) {
- Node node = representData(data);
- representedObjects.clear();
- objectToRepresent = null;
- return node;
+ protected final Map<Class<?>, Represent> representers = new HashMap<Class<?>, Represent>();
+ /**
+ * in Java 'null' is not a type. So we have to keep the null representer separately otherwise it
+ * will coincide with the default representer which is stored with the key null.
+ */
+ protected Represent nullRepresenter;
+ // the order is important (map can be also a sequence of key-values)
+ protected final Map<Class<?>, Represent> multiRepresenters =
+ new LinkedHashMap<Class<?>, Represent>();
+ protected DumperOptions.ScalarStyle defaultScalarStyle = null; // not explicitly defined
+ protected FlowStyle defaultFlowStyle = FlowStyle.AUTO;
+ protected final Map<Object, Node> representedObjects = new IdentityHashMap<Object, Node>() {
+ private static final long serialVersionUID = -5576159264232131854L;
+
+ public Node put(Object key, Node value) {
+ return super.put(key, new AnchorNode(value));
}
-
- protected final Node representData(Object data) {
- objectToRepresent = data;
- // check for identity
- if (representedObjects.containsKey(objectToRepresent)) {
- Node node = representedObjects.get(objectToRepresent);
- return node;
- }
- // }
- // check for null first
- if (data == null) {
- Node node = nullRepresenter.representData(null);
- return node;
- }
- // check the same class
- Node node;
- Class<?> clazz = data.getClass();
- if (representers.containsKey(clazz)) {
- Represent representer = representers.get(clazz);
- node = representer.representData(data);
- } else {
- // check the parents
- for (Class<?> repr : multiRepresenters.keySet()) {
- if (repr.isInstance(data)) {
- Represent representer = multiRepresenters.get(repr);
- node = representer.representData(data);
- return node;
- }
- }
-
- // check defaults
- if (multiRepresenters.containsKey(null)) {
- Represent representer = multiRepresenters.get(null);
- node = representer.representData(data);
- } else {
- Represent representer = representers.get(null);
- node = representer.representData(data);
- }
- }
- return node;
+ };
+
+ protected Object objectToRepresent;
+ private PropertyUtils propertyUtils;
+ private boolean explicitPropertyUtils = false;
+
+ public Node represent(Object data) {
+ Node node = representData(data);
+ representedObjects.clear();
+ objectToRepresent = null;
+ return node;
+ }
+
+ protected final Node representData(Object data) {
+ objectToRepresent = data;
+ // check for identity
+ if (representedObjects.containsKey(objectToRepresent)) {
+ Node node = representedObjects.get(objectToRepresent);
+ return node;
}
-
- protected Node representScalar(Tag tag, String value, Character style) {
- if (style == null) {
- style = this.defaultScalarStyle;
+ // }
+ // check for null first
+ if (data == null) {
+ Node node = nullRepresenter.representData(null);
+ return node;
+ }
+ // check the same class
+ Node node;
+ Class<?> clazz = data.getClass();
+ if (representers.containsKey(clazz)) {
+ Represent representer = representers.get(clazz);
+ node = representer.representData(data);
+ } else {
+ // check the parents
+ for (Class<?> repr : multiRepresenters.keySet()) {
+ if (repr != null && repr.isInstance(data)) {
+ Represent representer = multiRepresenters.get(repr);
+ node = representer.representData(data);
+ return node;
}
- Node node = new ScalarNode(tag, value, null, null, style);
- return node;
+ }
+
+ // check defaults
+ if (multiRepresenters.containsKey(null)) {
+ Represent representer = multiRepresenters.get(null);
+ node = representer.representData(data);
+ } else {
+ Represent representer = representers.get(null);
+ node = representer.representData(data);
+ }
}
+ return node;
+ }
- protected Node representScalar(Tag tag, String value) {
- return representScalar(tag, value, null);
+ protected Node representScalar(Tag tag, String value, DumperOptions.ScalarStyle style) {
+ if (style == null) {
+ style = this.defaultScalarStyle;
}
-
- protected Node representSequence(Tag tag, Iterable<?> sequence, Boolean flowStyle) {
- int size = 10;// default for ArrayList
- if (sequence instanceof List<?>) {
- size = ((List<?>) sequence).size();
- }
- List<Node> value = new ArrayList<Node>(size);
- SequenceNode node = new SequenceNode(tag, value, flowStyle);
- representedObjects.put(objectToRepresent, node);
- boolean bestStyle = true;
- for (Object item : sequence) {
- Node nodeItem = representData(item);
- if (!(nodeItem instanceof ScalarNode && ((ScalarNode) nodeItem).getStyle() == null)) {
- bestStyle = false;
- }
- value.add(nodeItem);
- }
- if (flowStyle == null) {
- if (defaultFlowStyle != FlowStyle.AUTO) {
- node.setFlowStyle(defaultFlowStyle.getStyleBoolean());
- } else {
- node.setFlowStyle(bestStyle);
- }
- }
- return node;
+ Node node = new ScalarNode(tag, value, null, null, style);
+ return node;
+ }
+
+ protected Node representScalar(Tag tag, String value) {
+ return representScalar(tag, value, null);
+ }
+
+ protected Node representSequence(Tag tag, Iterable<?> sequence,
+ DumperOptions.FlowStyle flowStyle) {
+ int size = 10;// default for ArrayList
+ if (sequence instanceof List<?>) {
+ size = ((List<?>) sequence).size();
}
-
- protected Node representMapping(Tag tag, Map<?, ?> mapping, Boolean flowStyle) {
- List<NodeTuple> value = new ArrayList<NodeTuple>(mapping.size());
- MappingNode node = new MappingNode(tag, value, flowStyle);
- representedObjects.put(objectToRepresent, node);
- boolean bestStyle = true;
- for (Map.Entry<?, ?> entry : mapping.entrySet()) {
- Node nodeKey = representData(entry.getKey());
- Node nodeValue = representData(entry.getValue());
- if (!(nodeKey instanceof ScalarNode && ((ScalarNode) nodeKey).getStyle() == null)) {
- bestStyle = false;
- }
- if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).getStyle() == null)) {
- bestStyle = false;
- }
- value.add(new NodeTuple(nodeKey, nodeValue));
- }
- if (flowStyle == null) {
- if (defaultFlowStyle != FlowStyle.AUTO) {
- node.setFlowStyle(defaultFlowStyle.getStyleBoolean());
- } else {
- node.setFlowStyle(bestStyle);
- }
- }
- return node;
+ List<Node> value = new ArrayList<Node>(size);
+ SequenceNode node = new SequenceNode(tag, value, flowStyle);
+ representedObjects.put(objectToRepresent, node);
+ DumperOptions.FlowStyle bestStyle = FlowStyle.FLOW;
+ for (Object item : sequence) {
+ Node nodeItem = representData(item);
+ if (!(nodeItem instanceof ScalarNode && ((ScalarNode) nodeItem).isPlain())) {
+ bestStyle = FlowStyle.BLOCK;
+ }
+ value.add(nodeItem);
}
-
- public void setDefaultScalarStyle(ScalarStyle defaultStyle) {
- this.defaultScalarStyle = defaultStyle.getChar();
+ if (flowStyle == FlowStyle.AUTO) {
+ if (defaultFlowStyle != FlowStyle.AUTO) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
}
-
- public void setDefaultFlowStyle(FlowStyle defaultFlowStyle) {
- this.defaultFlowStyle = defaultFlowStyle;
+ return node;
+ }
+
+ protected Node representMapping(Tag tag, Map<?, ?> mapping, DumperOptions.FlowStyle flowStyle) {
+ List<NodeTuple> value = new ArrayList<NodeTuple>(mapping.size());
+ MappingNode node = new MappingNode(tag, value, flowStyle);
+ representedObjects.put(objectToRepresent, node);
+ DumperOptions.FlowStyle bestStyle = FlowStyle.FLOW;
+ for (Map.Entry<?, ?> entry : mapping.entrySet()) {
+ Node nodeKey = representData(entry.getKey());
+ Node nodeValue = representData(entry.getValue());
+ if (!(nodeKey instanceof ScalarNode && ((ScalarNode) nodeKey).isPlain())) {
+ bestStyle = FlowStyle.BLOCK;
+ }
+ if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
+ bestStyle = FlowStyle.BLOCK;
+ }
+ value.add(new NodeTuple(nodeKey, nodeValue));
}
-
- public FlowStyle getDefaultFlowStyle() {
- return this.defaultFlowStyle;
+ if (flowStyle == FlowStyle.AUTO) {
+ if (defaultFlowStyle != FlowStyle.AUTO) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
}
+ return node;
+ }
- public void setPropertyUtils(PropertyUtils propertyUtils) {
- this.propertyUtils = propertyUtils;
- this.explicitPropertyUtils = true;
- }
+ public void setDefaultScalarStyle(ScalarStyle defaultStyle) {
+ this.defaultScalarStyle = defaultStyle;
+ }
- public final PropertyUtils getPropertyUtils() {
- if (propertyUtils == null) {
- propertyUtils = new PropertyUtils();
- }
- return propertyUtils;
+ public ScalarStyle getDefaultScalarStyle() {
+ if (defaultScalarStyle == null) {
+ return ScalarStyle.PLAIN;
}
+ return defaultScalarStyle;
+ }
+
+ public void setDefaultFlowStyle(FlowStyle defaultFlowStyle) {
+ this.defaultFlowStyle = defaultFlowStyle;
+ }
- public final boolean isExplicitPropertyUtils() {
- return explicitPropertyUtils;
+ public FlowStyle getDefaultFlowStyle() {
+ return this.defaultFlowStyle;
+ }
+
+ public void setPropertyUtils(PropertyUtils propertyUtils) {
+ this.propertyUtils = propertyUtils;
+ this.explicitPropertyUtils = true;
+ }
+
+ public final PropertyUtils getPropertyUtils() {
+ if (propertyUtils == null) {
+ propertyUtils = new PropertyUtils();
}
+ return propertyUtils;
+ }
+
+ public final boolean isExplicitPropertyUtils() {
+ return explicitPropertyUtils;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/Represent.java b/src/main/java/org/yaml/snakeyaml/representer/Represent.java
index 55629ed4..0b0d6ef1 100644
--- a/src/main/java/org/yaml/snakeyaml/representer/Represent.java
+++ b/src/main/java/org/yaml/snakeyaml/representer/Represent.java
@@ -1,36 +1,32 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.representer;
import org.yaml.snakeyaml.nodes.Node;
/**
- * Create a Node Graph out of the provided Native Data Structure (Java
- * instance).
- *
- * @see <a href="http://yaml.org/spec/1.1/#id859109">Chapter 3. Processing YAML
- * Information</a>
+ * Create a Node Graph out of the provided Native Data Structure (Java instance).
+ *
+ * @see <a href="http://yaml.org/spec/1.1/#id859109">Chapter 3. Processing YAML Information</a>
*/
public interface Represent {
- /**
- * Create a Node
- *
- * @param data
- * the instance to represent
- * @return Node to dump
- */
- Node representData(Object data);
+
+ /**
+ * Create a Node
+ *
+ * @param data the instance to represent
+ * @return Node to dump
+ */
+ Node representData(Object data);
}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/Representer.java b/src/main/java/org/yaml/snakeyaml/representer/Representer.java
index e83a5ea2..5376b6e2 100644
--- a/src/main/java/org/yaml/snakeyaml/representer/Representer.java
+++ b/src/main/java/org/yaml/snakeyaml/representer/Representer.java
@@ -1,32 +1,32 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.representer;
-import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
-import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
@@ -40,209 +40,225 @@ import org.yaml.snakeyaml.nodes.Tag;
*/
public class Representer extends SafeRepresenter {
- public Representer() {
- this.representers.put(null, new RepresentJavaBean());
+ protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = Collections.emptyMap();
+
+ public Representer() {
+ this.representers.put(null, new RepresentJavaBean());
+ }
+
+ public Representer(DumperOptions options) {
+ super(options);
+ this.representers.put(null, new RepresentJavaBean());
+ }
+
+ public TypeDescription addTypeDescription(TypeDescription td) {
+ if (Collections.EMPTY_MAP == typeDefinitions) {
+ typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
}
+ if (td.getTag() != null) {
+ addClassTag(td.getType(), td.getTag());
+ }
+ td.setPropertyUtils(getPropertyUtils());
+ return typeDefinitions.put(td.getType(), td);
+ }
- protected class RepresentJavaBean implements Represent {
- public Node representData(Object data) {
- try {
- return representJavaBean(getProperties(data.getClass()), data);
- } catch (IntrospectionException e) {
- throw new YAMLException(e);
- }
- }
+ @Override
+ public void setPropertyUtils(PropertyUtils propertyUtils) {
+ super.setPropertyUtils(propertyUtils);
+ Collection<TypeDescription> tds = typeDefinitions.values();
+ for (TypeDescription typeDescription : tds) {
+ typeDescription.setPropertyUtils(propertyUtils);
}
+ }
- /**
- * Tag logic:<br/>
- * - explicit root tag is set in serializer <br/>
- * - if there is a predefined class tag it is used<br/>
- * - a global tag with class name is always used as tag. The JavaBean parent
- * of the specified JavaBean may set another tag (tag:yaml.org,2002:map)
- * when the property class is the same as runtime class
- *
- * @param properties
- * JavaBean getters
- * @param javaBean
- * instance for Node
- * @return Node to get serialized
- */
- protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
- List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
- Tag tag;
- Tag customTag = classTags.get(javaBean.getClass());
- tag = customTag != null ? customTag : new Tag(javaBean.getClass());
- // flow style will be chosen by BaseRepresenter
- MappingNode node = new MappingNode(tag, value, null);
- representedObjects.put(javaBean, node);
- boolean bestStyle = true;
- for (Property property : properties) {
- Object memberValue = property.get(javaBean);
- Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue
- .getClass());
- NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue,
- customPropertyTag);
- if (tuple == null) {
- continue;
- }
- if (((ScalarNode) tuple.getKeyNode()).getStyle() != null) {
- bestStyle = false;
- }
- Node nodeValue = tuple.getValueNode();
- if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).getStyle() == null)) {
- bestStyle = false;
- }
- value.add(tuple);
- }
- if (defaultFlowStyle != FlowStyle.AUTO) {
- node.setFlowStyle(defaultFlowStyle.getStyleBoolean());
- } else {
- node.setFlowStyle(bestStyle);
- }
- return node;
+ protected class RepresentJavaBean implements Represent {
+
+ public Node representData(Object data) {
+ return representJavaBean(getProperties(data.getClass()), data);
+ }
+ }
+
+ /**
+ * Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
+ * used - a global tag with class name is always used as tag. The JavaBean parent of the specified
+ * JavaBean may set another tag (tag:yaml.org,2002:map) when the property class is the same as
+ * runtime class
+ *
+ * @param properties JavaBean getters
+ * @param javaBean instance for Node
+ * @return Node to get serialized
+ */
+ protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
+ List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
+ Tag tag;
+ Tag customTag = classTags.get(javaBean.getClass());
+ tag = customTag != null ? customTag : new Tag(javaBean.getClass());
+ // flow style will be chosen by BaseRepresenter
+ MappingNode node = new MappingNode(tag, value, FlowStyle.AUTO);
+ representedObjects.put(javaBean, node);
+ DumperOptions.FlowStyle bestStyle = FlowStyle.FLOW;
+ for (Property property : properties) {
+ Object memberValue = property.get(javaBean);
+ Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue.getClass());
+ NodeTuple tuple =
+ representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
+ if (tuple == null) {
+ continue;
+ }
+ if (!((ScalarNode) tuple.getKeyNode()).isPlain()) {
+ bestStyle = FlowStyle.BLOCK;
+ }
+ Node nodeValue = tuple.getValueNode();
+ if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
+ bestStyle = FlowStyle.BLOCK;
+ }
+ value.add(tuple);
}
+ if (defaultFlowStyle != FlowStyle.AUTO) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
+ return node;
+ }
+
+ /**
+ * Represent one JavaBean property.
+ *
+ * @param javaBean - the instance to be represented
+ * @param property - the property of the instance
+ * @param propertyValue - value to be represented
+ * @param customTag - user defined Tag
+ * @return NodeTuple to be used in a MappingNode. Return null to skip the property
+ */
+ protected NodeTuple representJavaBeanProperty(Object javaBean, Property property,
+ Object propertyValue, Tag customTag) {
+ ScalarNode nodeKey = (ScalarNode) representData(property.getName());
+ // the first occurrence of the node must keep the tag
+ boolean hasAlias = this.representedObjects.containsKey(propertyValue);
- /**
- * Represent one JavaBean property.
- *
- * @param javaBean
- * - the instance to be represented
- * @param property
- * - the property of the instance
- * @param propertyValue
- * - value to be represented
- * @param customTag
- * - user defined Tag
- * @return NodeTuple to be used in a MappingNode. Return null to skip the
- * property
- */
- protected NodeTuple representJavaBeanProperty(Object javaBean, Property property,
- Object propertyValue, Tag customTag) {
- ScalarNode nodeKey = (ScalarNode) representData(property.getName());
- // the first occurrence of the node must keep the tag
- boolean hasAlias = this.representedObjects.containsKey(propertyValue);
-
- Node nodeValue = representData(propertyValue);
-
- if (propertyValue != null && !hasAlias) {
- NodeId nodeId = nodeValue.getNodeId();
- if (customTag == null) {
- if (nodeId == NodeId.scalar) {
- if (propertyValue instanceof Enum<?>) {
- nodeValue.setTag(Tag.STR);
- }
- } else {
- if (nodeId == NodeId.mapping) {
- if (property.getType() == propertyValue.getClass()) {
- if (!(propertyValue instanceof Map<?, ?>)) {
- if (!nodeValue.getTag().equals(Tag.SET)) {
- nodeValue.setTag(Tag.MAP);
- }
- }
- }
- }
- checkGlobalTag(property, nodeValue, propertyValue);
+ Node nodeValue = representData(propertyValue);
+
+ if (propertyValue != null && !hasAlias) {
+ NodeId nodeId = nodeValue.getNodeId();
+ if (customTag == null) {
+ if (nodeId == NodeId.scalar) {
+ // generic Enum requires the full tag
+ if (property.getType() != java.lang.Enum.class) {
+ if (propertyValue instanceof Enum<?>) {
+ nodeValue.setTag(Tag.STR);
+ }
+ }
+ } else {
+ if (nodeId == NodeId.mapping) {
+ if (property.getType() == propertyValue.getClass()) {
+ if (!(propertyValue instanceof Map<?, ?>)) {
+ if (!nodeValue.getTag().equals(Tag.SET)) {
+ nodeValue.setTag(Tag.MAP);
}
+ }
}
+ }
+ checkGlobalTag(property, nodeValue, propertyValue);
}
+ }
+ }
- return new NodeTuple(nodeKey, nodeValue);
+ return new NodeTuple(nodeKey, nodeValue);
+ }
+
+ /**
+ * Remove redundant global tag for a type safe (generic) collection if it is the same as defined
+ * by the JavaBean property
+ *
+ * @param property - JavaBean property
+ * @param node - representation of the property
+ * @param object - instance represented by the node
+ */
+ @SuppressWarnings("unchecked")
+ protected void checkGlobalTag(Property property, Node node, Object object) {
+ // Skip primitive arrays.
+ if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
+ return;
}
- /**
- * Remove redundant global tag for a type safe (generic) collection if it is
- * the same as defined by the JavaBean property
- *
- * @param property
- * - JavaBean property
- * @param node
- * - representation of the property
- * @param object
- * - instance represented by the node
- */
- @SuppressWarnings("unchecked")
- protected void checkGlobalTag(Property property, Node node, Object object) {
- // Skip primitive arrays.
- if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
- return;
+ Class<?>[] arguments = property.getActualTypeArguments();
+ if (arguments != null) {
+ if (node.getNodeId() == NodeId.sequence) {
+ // apply map tag where class is the same
+ Class<? extends Object> t = arguments[0];
+ SequenceNode snode = (SequenceNode) node;
+ Iterable<Object> memberList = Collections.EMPTY_LIST;
+ if (object.getClass().isArray()) {
+ memberList = Arrays.asList((Object[]) object);
+ } else if (object instanceof Iterable<?>) {
+ // list
+ memberList = (Iterable<Object>) object;
}
-
- Class<?>[] arguments = property.getActualTypeArguments();
- if (arguments != null) {
- if (node.getNodeId() == NodeId.sequence) {
- // apply map tag where class is the same
- Class<? extends Object> t = arguments[0];
- SequenceNode snode = (SequenceNode) node;
- Iterable<Object> memberList = Collections.EMPTY_LIST;
- if (object.getClass().isArray()) {
- memberList = Arrays.asList((Object[]) object);
- } else if (object instanceof Iterable<?>) {
- // list
- memberList = (Iterable<Object>) object;
+ Iterator<Object> iter = memberList.iterator();
+ if (iter.hasNext()) {
+ for (Node childNode : snode.getValue()) {
+ Object member = iter.next();
+ if (member != null) {
+ if (t.equals(member.getClass())) {
+ if (childNode.getNodeId() == NodeId.mapping) {
+ childNode.setTag(Tag.MAP);
}
- Iterator<Object> iter = memberList.iterator();
- if (iter.hasNext()) {
- for (Node childNode : snode.getValue()) {
- Object member = iter.next();
- if (member != null) {
- if (t.equals(member.getClass()))
- if (childNode.getNodeId() == NodeId.mapping) {
- childNode.setTag(Tag.MAP);
- }
- }
- }
- }
- } else if (object instanceof Set) {
- Class<?> t = arguments[0];
- MappingNode mnode = (MappingNode) node;
- Iterator<NodeTuple> iter = mnode.getValue().iterator();
- Set<?> set = (Set<?>) object;
- for (Object member : set) {
- NodeTuple tuple = iter.next();
- Node keyNode = tuple.getKeyNode();
- if (t.equals(member.getClass())) {
- if (keyNode.getNodeId() == NodeId.mapping) {
- keyNode.setTag(Tag.MAP);
- }
- }
- }
- } else if (object instanceof Map) {
- Class<?> keyType = arguments[0];
- Class<?> valueType = arguments[1];
- MappingNode mnode = (MappingNode) node;
- for (NodeTuple tuple : mnode.getValue()) {
- resetTag(keyType, tuple.getKeyNode());
- resetTag(valueType, tuple.getValueNode());
- }
- } else {
- // the type for collection entries cannot be
- // detected
+ }
}
+ }
}
- }
-
- private void resetTag(Class<? extends Object> type, Node node) {
- Tag tag = node.getTag();
- if (tag.matches(type)) {
- if (Enum.class.isAssignableFrom(type)) {
- node.setTag(Tag.STR);
- } else {
- node.setTag(Tag.MAP);
+ } else if (object instanceof Set) {
+ Class<?> t = arguments[0];
+ MappingNode mnode = (MappingNode) node;
+ Iterator<NodeTuple> iter = mnode.getValue().iterator();
+ Set<?> set = (Set<?>) object;
+ for (Object member : set) {
+ NodeTuple tuple = iter.next();
+ Node keyNode = tuple.getKeyNode();
+ if (t.equals(member.getClass())) {
+ if (keyNode.getNodeId() == NodeId.mapping) {
+ keyNode.setTag(Tag.MAP);
}
+ }
+ }
+ } else if (object instanceof Map) { // NodeId.mapping ends-up here
+ Class<?> keyType = arguments[0];
+ Class<?> valueType = arguments[1];
+ MappingNode mnode = (MappingNode) node;
+ for (NodeTuple tuple : mnode.getValue()) {
+ resetTag(keyType, tuple.getKeyNode());
+ resetTag(valueType, tuple.getValueNode());
}
+ } else {
+ // the type for collection entries cannot be
+ // detected
+ }
+ }
+ }
+
+ private void resetTag(Class<? extends Object> type, Node node) {
+ Tag tag = node.getTag();
+ if (tag.matches(type)) {
+ if (Enum.class.isAssignableFrom(type)) {
+ node.setTag(Tag.STR);
+ } else {
+ node.setTag(Tag.MAP);
+ }
}
+ }
- /**
- * Get JavaBean properties to be serialised. The order is respected. This
- * method may be overridden to provide custom property selection or order.
- *
- * @param type
- * - JavaBean to inspect the properties
- * @return properties to serialise
- */
- protected Set<Property> getProperties(Class<? extends Object> type)
- throws IntrospectionException {
- return getPropertyUtils().getProperties(type);
+ /**
+ * Get JavaBean properties to be serialised. The order is respected. This method may be overridden
+ * to provide custom property selection or order.
+ *
+ * @param type - JavaBean to inspect the properties
+ * @return properties to serialise
+ */
+ protected Set<Property> getProperties(Class<? extends Object> type) {
+ if (typeDefinitions.containsKey(type)) {
+ return typeDefinitions.get(type).getProperties();
}
+ return getPropertyUtils().getProperties(type);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java b/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java
index 147e3af5..b46e07fe 100644
--- a/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java
+++ b/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java
@@ -1,22 +1,20 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.representer;
-import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -30,7 +28,7 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Pattern;
-
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import org.yaml.snakeyaml.nodes.Node;
@@ -42,385 +40,432 @@ import org.yaml.snakeyaml.reader.StreamReader;
*/
class SafeRepresenter extends BaseRepresenter {
- protected Map<Class<? extends Object>, Tag> classTags;
- protected TimeZone timeZone = null;
-
- public SafeRepresenter() {
- this.nullRepresenter = new RepresentNull();
- this.representers.put(String.class, new RepresentString());
- this.representers.put(Boolean.class, new RepresentBoolean());
- this.representers.put(Character.class, new RepresentString());
- this.representers.put(UUID.class, new RepresentUuid());
- this.representers.put(byte[].class, new RepresentByteArray());
-
- Represent primitiveArray = new RepresentPrimitiveArray();
- representers.put(short[].class, primitiveArray);
- representers.put(int[].class, primitiveArray);
- representers.put(long[].class, primitiveArray);
- representers.put(float[].class, primitiveArray);
- representers.put(double[].class, primitiveArray);
- representers.put(char[].class, primitiveArray);
- representers.put(boolean[].class, primitiveArray);
-
- this.multiRepresenters.put(Number.class, new RepresentNumber());
- this.multiRepresenters.put(List.class, new RepresentList());
- this.multiRepresenters.put(Map.class, new RepresentMap());
- this.multiRepresenters.put(Set.class, new RepresentSet());
- this.multiRepresenters.put(Iterator.class, new RepresentIterator());
- this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray());
- this.multiRepresenters.put(Date.class, new RepresentDate());
- this.multiRepresenters.put(Enum.class, new RepresentEnum());
- this.multiRepresenters.put(Calendar.class, new RepresentDate());
- classTags = new HashMap<Class<? extends Object>, Tag>();
+ protected Map<Class<? extends Object>, Tag> classTags;
+ protected TimeZone timeZone = null;
+ protected DumperOptions.NonPrintableStyle nonPrintableStyle;
+
+ public SafeRepresenter() {
+ this(new DumperOptions());
+ }
+
+ public SafeRepresenter(DumperOptions options) {
+ this.nullRepresenter = new RepresentNull();
+ this.representers.put(String.class, new RepresentString());
+ this.representers.put(Boolean.class, new RepresentBoolean());
+ this.representers.put(Character.class, new RepresentString());
+ this.representers.put(UUID.class, new RepresentUuid());
+ this.representers.put(byte[].class, new RepresentByteArray());
+
+ Represent primitiveArray = new RepresentPrimitiveArray();
+ representers.put(short[].class, primitiveArray);
+ representers.put(int[].class, primitiveArray);
+ representers.put(long[].class, primitiveArray);
+ representers.put(float[].class, primitiveArray);
+ representers.put(double[].class, primitiveArray);
+ representers.put(char[].class, primitiveArray);
+ representers.put(boolean[].class, primitiveArray);
+
+ this.multiRepresenters.put(Number.class, new RepresentNumber());
+ this.multiRepresenters.put(List.class, new RepresentList());
+ this.multiRepresenters.put(Map.class, new RepresentMap());
+ this.multiRepresenters.put(Set.class, new RepresentSet());
+ this.multiRepresenters.put(Iterator.class, new RepresentIterator());
+ this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray());
+ this.multiRepresenters.put(Date.class, new RepresentDate());
+ this.multiRepresenters.put(Enum.class, new RepresentEnum());
+ this.multiRepresenters.put(Calendar.class, new RepresentDate());
+ classTags = new HashMap<Class<? extends Object>, Tag>();
+ this.nonPrintableStyle = options.getNonPrintableStyle();
+ }
+
+ protected Tag getTag(Class<?> clazz, Tag defaultTag) {
+ if (classTags.containsKey(clazz)) {
+ return classTags.get(clazz);
+ } else {
+ return defaultTag;
+ }
+ }
+
+ /**
+ * Define a tag for the <code>Class</code> to serialize.
+ *
+ * @param clazz <code>Class</code> which tag is changed
+ * @param tag new tag to be used for every instance of the specified <code>Class</code>
+ * @return the previous tag associated with the <code>Class</code>
+ */
+ public Tag addClassTag(Class<? extends Object> clazz, Tag tag) {
+ if (tag == null) {
+ throw new NullPointerException("Tag must be provided.");
}
+ return classTags.put(clazz, tag);
+ }
- protected Tag getTag(Class<?> clazz, Tag defaultTag) {
- if (classTags.containsKey(clazz)) {
- return classTags.get(clazz);
- } else {
- return defaultTag;
+ protected class RepresentNull implements Represent {
+
+ public Node representData(Object data) {
+ return representScalar(Tag.NULL, "null");
+ }
+ }
+
+ private static final Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029");
+
+ protected class RepresentString implements Represent {
+
+ public Node representData(Object data) {
+ Tag tag = Tag.STR;
+ DumperOptions.ScalarStyle style = null;// not defined
+ String value = data.toString();
+ if (nonPrintableStyle == DumperOptions.NonPrintableStyle.BINARY
+ && !StreamReader.isPrintable(value)) {
+ tag = Tag.BINARY;
+ char[] binary;
+ final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+ // sometimes above will just silently fail - it will return incomplete data
+ // it happens when String has invalid code points
+ // (for example half surrogate character without other half)
+ final String checkValue = new String(bytes, StandardCharsets.UTF_8);
+ if (!checkValue.equals(value)) {
+ throw new YAMLException("invalid string value has occurred");
}
+ binary = Base64Coder.encode(bytes);
+ value = String.valueOf(binary);
+ style = DumperOptions.ScalarStyle.LITERAL;
+ }
+ // if no other scalar style is explicitly set, use literal style for
+ // multiline scalars
+ if (defaultScalarStyle == DumperOptions.ScalarStyle.PLAIN
+ && MULTILINE_PATTERN.matcher(value).find()) {
+ style = DumperOptions.ScalarStyle.LITERAL;
+ }
+ return representScalar(tag, value, style);
}
-
- /**
- * Define a tag for the <code>Class</code> to serialize.
- *
- * @param clazz
- * <code>Class</code> which tag is changed
- * @param tag
- * new tag to be used for every instance of the specified
- * <code>Class</code>
- * @return the previous tag associated with the <code>Class</code>
- */
- public Tag addClassTag(Class<? extends Object> clazz, Tag tag) {
- if (tag == null) {
- throw new NullPointerException("Tag must be provided.");
+ }
+
+ protected class RepresentBoolean implements Represent {
+
+ public Node representData(Object data) {
+ String value;
+ if (Boolean.TRUE.equals(data)) {
+ value = "true";
+ } else {
+ value = "false";
+ }
+ return representScalar(Tag.BOOL, value);
+ }
+ }
+
+ protected class RepresentNumber implements Represent {
+
+ public Node representData(Object data) {
+ Tag tag;
+ String value;
+ if (data instanceof Byte || data instanceof Short || data instanceof Integer
+ || data instanceof Long || data instanceof BigInteger) {
+ tag = Tag.INT;
+ value = data.toString();
+ } else {
+ Number number = (Number) data;
+ tag = Tag.FLOAT;
+ if (number.equals(Double.NaN)) {
+ value = ".NaN";
+ } else if (number.equals(Double.POSITIVE_INFINITY)) {
+ value = ".inf";
+ } else if (number.equals(Double.NEGATIVE_INFINITY)) {
+ value = "-.inf";
+ } else {
+ value = number.toString();
}
- return classTags.put(clazz, tag);
+ }
+ return representScalar(getTag(data.getClass(), tag), value);
}
+ }
- protected class RepresentNull implements Represent {
- public Node representData(Object data) {
- return representScalar(Tag.NULL, "null");
- }
+ protected class RepresentList implements Represent {
+
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ return representSequence(getTag(data.getClass(), Tag.SEQ), (List<Object>) data,
+ DumperOptions.FlowStyle.AUTO);
}
+ }
- public static Pattern MULTILINE_PATTERN = Pattern.compile("\n|\u0085|\u2028|\u2029");
-
- protected class RepresentString implements Represent {
- public Node representData(Object data) {
- Tag tag = Tag.STR;
- Character style = null;
- String value = data.toString();
- if (StreamReader.NON_PRINTABLE.matcher(value).find()) {
- tag = Tag.BINARY;
- char[] binary;
- try {
- binary = Base64Coder.encode(value.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException e) {
- throw new YAMLException(e);
- }
- value = String.valueOf(binary);
- style = '|';
- }
- // if no other scalar style is explicitly set, use literal style for
- // multiline scalars
- if (defaultScalarStyle == null && MULTILINE_PATTERN.matcher(value).find()) {
- style = '|';
- }
- return representScalar(tag, value, style);
- }
+ protected class RepresentIterator implements Represent {
+
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ Iterator<Object> iter = (Iterator<Object>) data;
+ return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter),
+ DumperOptions.FlowStyle.AUTO);
}
+ }
- protected class RepresentBoolean implements Represent {
- public Node representData(Object data) {
- String value;
- if (Boolean.TRUE.equals(data)) {
- value = "true";
- } else {
- value = "false";
- }
- return representScalar(Tag.BOOL, value);
- }
+ private static class IteratorWrapper implements Iterable<Object> {
+
+ private final Iterator<Object> iter;
+
+ public IteratorWrapper(Iterator<Object> iter) {
+ this.iter = iter;
}
- protected class RepresentNumber implements Represent {
- public Node representData(Object data) {
- Tag tag;
- String value;
- if (data instanceof Byte || data instanceof Short || data instanceof Integer
- || data instanceof Long || data instanceof BigInteger) {
- tag = Tag.INT;
- value = data.toString();
- } else {
- Number number = (Number) data;
- tag = Tag.FLOAT;
- if (number.equals(Double.NaN)) {
- value = ".NaN";
- } else if (number.equals(Double.POSITIVE_INFINITY)) {
- value = ".inf";
- } else if (number.equals(Double.NEGATIVE_INFINITY)) {
- value = "-.inf";
- } else {
- value = number.toString();
- }
- }
- return representScalar(getTag(data.getClass(), tag), value);
- }
+ public Iterator<Object> iterator() {
+ return iter;
}
+ }
- protected class RepresentList implements Represent {
- @SuppressWarnings("unchecked")
- public Node representData(Object data) {
- return representSequence(getTag(data.getClass(), Tag.SEQ), (List<Object>) data, null);
- }
+ protected class RepresentArray implements Represent {
+
+ public Node representData(Object data) {
+ Object[] array = (Object[]) data;
+ List<Object> list = Arrays.asList(array);
+ return representSequence(Tag.SEQ, list, DumperOptions.FlowStyle.AUTO);
+ }
+ }
+
+ /**
+ * Represents primitive arrays, such as short[] and float[], by converting them into equivalent
+ * List<Short> and List<Float> using the appropriate autoboxing type.
+ */
+ protected class RepresentPrimitiveArray implements Represent {
+
+ public Node representData(Object data) {
+ Class<?> type = data.getClass().getComponentType();
+
+ if (byte.class == type) {
+ return representSequence(Tag.SEQ, asByteList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (short.class == type) {
+ return representSequence(Tag.SEQ, asShortList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (int.class == type) {
+ return representSequence(Tag.SEQ, asIntList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (long.class == type) {
+ return representSequence(Tag.SEQ, asLongList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (float.class == type) {
+ return representSequence(Tag.SEQ, asFloatList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (double.class == type) {
+ return representSequence(Tag.SEQ, asDoubleList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (char.class == type) {
+ return representSequence(Tag.SEQ, asCharList(data), DumperOptions.FlowStyle.AUTO);
+ } else if (boolean.class == type) {
+ return representSequence(Tag.SEQ, asBooleanList(data), DumperOptions.FlowStyle.AUTO);
+ }
+
+ throw new YAMLException("Unexpected primitive '" + type.getCanonicalName() + "'");
}
- protected class RepresentIterator implements Represent {
- @SuppressWarnings("unchecked")
- public Node representData(Object data) {
- Iterator<Object> iter = (Iterator<Object>) data;
- return representSequence(getTag(data.getClass(), Tag.SEQ), new IteratorWrapper(iter),
- null);
- }
+ private List<Byte> asByteList(Object in) {
+ byte[] array = (byte[]) in;
+ List<Byte> list = new ArrayList<Byte>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
}
- private static class IteratorWrapper implements Iterable<Object> {
- private Iterator<Object> iter;
+ private List<Short> asShortList(Object in) {
+ short[] array = (short[]) in;
+ List<Short> list = new ArrayList<Short>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
+ }
- public IteratorWrapper(Iterator<Object> iter) {
- this.iter = iter;
- }
+ private List<Integer> asIntList(Object in) {
+ int[] array = (int[]) in;
+ List<Integer> list = new ArrayList<Integer>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
+ }
- public Iterator<Object> iterator() {
- return iter;
- }
+ private List<Long> asLongList(Object in) {
+ long[] array = (long[]) in;
+ List<Long> list = new ArrayList<Long>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
}
- protected class RepresentArray implements Represent {
- public Node representData(Object data) {
- Object[] array = (Object[]) data;
- List<Object> list = Arrays.asList(array);
- return representSequence(Tag.SEQ, list, null);
- }
+ private List<Float> asFloatList(Object in) {
+ float[] array = (float[]) in;
+ List<Float> list = new ArrayList<Float>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
}
- /**
- * Represents primitive arrays, such as short[] and float[], by converting
- * them into equivalent List<Short> and List<Float> using the appropriate
- * autoboxing type.
- */
- protected class RepresentPrimitiveArray implements Represent {
- public Node representData(Object data) {
- Class<?> type = data.getClass().getComponentType();
-
- if (byte.class == type) {
- return representSequence(Tag.SEQ, asByteList(data), null);
- } else if (short.class == type) {
- return representSequence(Tag.SEQ, asShortList(data), null);
- } else if (int.class == type) {
- return representSequence(Tag.SEQ, asIntList(data), null);
- } else if (long.class == type) {
- return representSequence(Tag.SEQ, asLongList(data), null);
- } else if (float.class == type) {
- return representSequence(Tag.SEQ, asFloatList(data), null);
- } else if (double.class == type) {
- return representSequence(Tag.SEQ, asDoubleList(data), null);
- } else if (char.class == type) {
- return representSequence(Tag.SEQ, asCharList(data), null);
- } else if (boolean.class == type) {
- return representSequence(Tag.SEQ, asBooleanList(data), null);
- }
-
- throw new YAMLException("Unexpected primitive '" + type.getCanonicalName() + "'");
- }
+ private List<Double> asDoubleList(Object in) {
+ double[] array = (double[]) in;
+ List<Double> list = new ArrayList<Double>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
+ }
- private List<Byte> asByteList(Object in) {
- byte[] array = (byte[]) in;
- List<Byte> list = new ArrayList<Byte>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
- }
+ private List<Character> asCharList(Object in) {
+ char[] array = (char[]) in;
+ List<Character> list = new ArrayList<Character>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
+ }
- private List<Short> asShortList(Object in) {
- short[] array = (short[]) in;
- List<Short> list = new ArrayList<Short>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
- }
+ private List<Boolean> asBooleanList(Object in) {
+ boolean[] array = (boolean[]) in;
+ List<Boolean> list = new ArrayList<Boolean>(array.length);
+ for (int i = 0; i < array.length; ++i) {
+ list.add(array[i]);
+ }
+ return list;
+ }
+ }
- private List<Integer> asIntList(Object in) {
- int[] array = (int[]) in;
- List<Integer> list = new ArrayList<Integer>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
- }
+ protected class RepresentMap implements Represent {
- private List<Long> asLongList(Object in) {
- long[] array = (long[]) in;
- List<Long> list = new ArrayList<Long>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ return representMapping(getTag(data.getClass(), Tag.MAP), (Map<Object, Object>) data,
+ DumperOptions.FlowStyle.AUTO);
+ }
+ }
+
+ protected class RepresentSet implements Represent {
+
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ Map<Object, Object> value = new LinkedHashMap<Object, Object>();
+ Set<Object> set = (Set<Object>) data;
+ for (Object key : set) {
+ value.put(key, null);
+ }
+ return representMapping(getTag(data.getClass(), Tag.SET), value,
+ DumperOptions.FlowStyle.AUTO);
+ }
+ }
+
+ protected class RepresentDate implements Represent {
+
+ public Node representData(Object data) {
+ // because SimpleDateFormat ignores timezone we have to use Calendar
+ Calendar calendar;
+ if (data instanceof Calendar) {
+ calendar = (Calendar) data;
+ } else {
+ calendar =
+ Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC") : timeZone);
+ calendar.setTime((Date) data);
+ }
+ int years = calendar.get(Calendar.YEAR);
+ int months = calendar.get(Calendar.MONTH) + 1; // 0..12
+ int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31
+ int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24
+ int minutes = calendar.get(Calendar.MINUTE); // 0..59
+ int seconds = calendar.get(Calendar.SECOND); // 0..59
+ int millis = calendar.get(Calendar.MILLISECOND);
+ StringBuilder buffer = new StringBuilder(String.valueOf(years));
+ while (buffer.length() < 4) {
+ // ancient years
+ buffer.insert(0, "0");
+ }
+ buffer.append("-");
+ if (months < 10) {
+ buffer.append("0");
+ }
+ buffer.append(months);
+ buffer.append("-");
+ if (days < 10) {
+ buffer.append("0");
+ }
+ buffer.append(days);
+ buffer.append("T");
+ if (hour24 < 10) {
+ buffer.append("0");
+ }
+ buffer.append(hour24);
+ buffer.append(":");
+ if (minutes < 10) {
+ buffer.append("0");
+ }
+ buffer.append(minutes);
+ buffer.append(":");
+ if (seconds < 10) {
+ buffer.append("0");
+ }
+ buffer.append(seconds);
+ if (millis > 0) {
+ if (millis < 10) {
+ buffer.append(".00");
+ } else if (millis < 100) {
+ buffer.append(".0");
+ } else {
+ buffer.append(".");
}
-
- private List<Float> asFloatList(Object in) {
- float[] array = (float[]) in;
- List<Float> list = new ArrayList<Float>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
+ buffer.append(millis);
+ }
+
+ // Get the offset from GMT taking DST into account
+ int gmtOffset = calendar.getTimeZone().getOffset(calendar.getTime().getTime());
+ if (gmtOffset == 0) {
+ buffer.append('Z');
+ } else {
+ if (gmtOffset < 0) {
+ buffer.append('-');
+ gmtOffset *= -1;
+ } else {
+ buffer.append('+');
}
+ int minutesOffset = gmtOffset / (60 * 1000);
+ int hoursOffset = minutesOffset / 60;
+ int partOfHour = minutesOffset % 60;
- private List<Double> asDoubleList(Object in) {
- double[] array = (double[]) in;
- List<Double> list = new ArrayList<Double>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
+ if (hoursOffset < 10) {
+ buffer.append('0');
}
-
- private List<Character> asCharList(Object in) {
- char[] array = (char[]) in;
- List<Character> list = new ArrayList<Character>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
+ buffer.append(hoursOffset);
+ buffer.append(':');
+ if (partOfHour < 10) {
+ buffer.append('0');
}
+ buffer.append(partOfHour);
+ }
- private List<Boolean> asBooleanList(Object in) {
- boolean[] array = (boolean[]) in;
- List<Boolean> list = new ArrayList<Boolean>(array.length);
- for (int i = 0; i < array.length; ++i)
- list.add(array[i]);
- return list;
- }
+ return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(),
+ DumperOptions.ScalarStyle.PLAIN);
}
+ }
- protected class RepresentMap implements Represent {
- @SuppressWarnings("unchecked")
- public Node representData(Object data) {
- return representMapping(getTag(data.getClass(), Tag.MAP), (Map<Object, Object>) data,
- null);
- }
- }
+ protected class RepresentEnum implements Represent {
- protected class RepresentSet implements Represent {
- @SuppressWarnings("unchecked")
- public Node representData(Object data) {
- Map<Object, Object> value = new LinkedHashMap<Object, Object>();
- Set<Object> set = (Set<Object>) data;
- for (Object key : set) {
- value.put(key, null);
- }
- return representMapping(getTag(data.getClass(), Tag.SET), value, null);
- }
+ public Node representData(Object data) {
+ Tag tag = new Tag(data.getClass());
+ return representScalar(getTag(data.getClass(), tag), ((Enum<?>) data).name());
}
+ }
- protected class RepresentDate implements Represent {
- public Node representData(Object data) {
- // because SimpleDateFormat ignores timezone we have to use Calendar
- Calendar calendar;
- if (data instanceof Calendar) {
- calendar = (Calendar) data;
- } else {
- calendar = Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC")
- : timeZone);
- calendar.setTime((Date) data);
- }
- int years = calendar.get(Calendar.YEAR);
- int months = calendar.get(Calendar.MONTH) + 1; // 0..12
- int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31
- int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24
- int minutes = calendar.get(Calendar.MINUTE); // 0..59
- int seconds = calendar.get(Calendar.SECOND); // 0..59
- int millis = calendar.get(Calendar.MILLISECOND);
- StringBuilder buffer = new StringBuilder(String.valueOf(years));
- while (buffer.length() < 4) {
- // ancient years
- buffer.insert(0, "0");
- }
- buffer.append("-");
- if (months < 10) {
- buffer.append("0");
- }
- buffer.append(String.valueOf(months));
- buffer.append("-");
- if (days < 10) {
- buffer.append("0");
- }
- buffer.append(String.valueOf(days));
- buffer.append("T");
- if (hour24 < 10) {
- buffer.append("0");
- }
- buffer.append(String.valueOf(hour24));
- buffer.append(":");
- if (minutes < 10) {
- buffer.append("0");
- }
- buffer.append(String.valueOf(minutes));
- buffer.append(":");
- if (seconds < 10) {
- buffer.append("0");
- }
- buffer.append(String.valueOf(seconds));
- if (millis > 0) {
- if (millis < 10) {
- buffer.append(".00");
- } else if (millis < 100) {
- buffer.append(".0");
- } else {
- buffer.append(".");
- }
- buffer.append(String.valueOf(millis));
- }
- if (TimeZone.getTimeZone("UTC").equals(calendar.getTimeZone())) {
- buffer.append("Z");
- } else {
- // Get the Offset from GMT taking DST into account
- int gmtOffset = calendar.getTimeZone().getOffset(calendar.get(Calendar.ERA),
- calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
- calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_WEEK),
- calendar.get(Calendar.MILLISECOND));
- int minutesOffset = gmtOffset / (60 * 1000);
- int hoursOffset = minutesOffset / 60;
- int partOfHour = minutesOffset % 60;
- buffer.append((hoursOffset > 0 ? "+" : "") + hoursOffset + ":"
- + (partOfHour < 10 ? "0" + partOfHour : partOfHour));
- }
- return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), buffer.toString(), null);
- }
- }
+ protected class RepresentByteArray implements Represent {
- protected class RepresentEnum implements Represent {
- public Node representData(Object data) {
- Tag tag = new Tag(data.getClass());
- return representScalar(getTag(data.getClass(), tag), ((Enum<?>) data).name());
- }
+ public Node representData(Object data) {
+ char[] binary = Base64Coder.encode((byte[]) data);
+ return representScalar(Tag.BINARY, String.valueOf(binary), DumperOptions.ScalarStyle.LITERAL);
}
+ }
- protected class RepresentByteArray implements Represent {
- public Node representData(Object data) {
- char[] binary = Base64Coder.encode((byte[]) data);
- return representScalar(Tag.BINARY, String.valueOf(binary), '|');
- }
- }
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
- public TimeZone getTimeZone() {
- return timeZone;
- }
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
- public void setTimeZone(TimeZone timeZone) {
- this.timeZone = timeZone;
- }
+ protected class RepresentUuid implements Represent {
- protected class RepresentUuid implements Represent {
- public Node representData(Object data) {
- return representScalar(getTag(data.getClass(), new Tag(UUID.class)), data.toString());
- }
+ public Node representData(Object data) {
+ return representScalar(getTag(data.getClass(), new Tag(UUID.class)), data.toString());
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/resolver/Resolver.java b/src/main/java/org/yaml/snakeyaml/resolver/Resolver.java
index f8ec36fa..38960561 100644
--- a/src/main/java/org/yaml/snakeyaml/resolver/Resolver.java
+++ b/src/main/java/org/yaml/snakeyaml/resolver/Resolver.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.resolver;
@@ -20,7 +18,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
-
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.Tag;
@@ -28,111 +25,123 @@ import org.yaml.snakeyaml.nodes.Tag;
* Resolver tries to detect a type by content (when the tag is implicit)
*/
public class Resolver {
- public static final Pattern BOOL = Pattern
- .compile("^(?:yes|Yes|YES|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$");
- /**
- * The regular expression is taken from the 1.2 specification but '_'s are
- * added to keep backwards compatibility
- */
- public static final Pattern FLOAT = Pattern
- .compile("^([-+]?(\\.[0-9]+|[0-9_]+(\\.[0-9_]*)?)([eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");
- public static final Pattern INT = Pattern
- .compile("^(?:[-+]?0b[0-1_]+|[-+]?0[0-7_]+|[-+]?(?:0|[1-9][0-9_]*)|[-+]?0x[0-9a-fA-F_]+|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$");
- public static final Pattern MERGE = Pattern.compile("^(?:<<)$");
- public static final Pattern NULL = Pattern.compile("^(?:~|null|Null|NULL| )$");
- public static final Pattern EMPTY = Pattern.compile("^$");
- public static final Pattern TIMESTAMP = Pattern
- .compile("^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?(?:[Tt]|[ \t]+)[0-9][0-9]?:[0-9][0-9]:[0-9][0-9](?:\\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$");
- public static final Pattern VALUE = Pattern.compile("^(?:=)$");
- public static final Pattern YAML = Pattern.compile("^(?:!|&|\\*)$");
+ public static final Pattern BOOL = Pattern
+ .compile("^(?:yes|Yes|YES|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$");
- protected Map<Character, List<ResolverTuple>> yamlImplicitResolvers = new HashMap<Character, List<ResolverTuple>>();
+ /**
+ * The regular expression is taken from the 1.2 specification but '_'s are added to keep backwards
+ * compatibility
+ */
+ public static final Pattern FLOAT =
+ Pattern.compile("^(" + "[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?" + // (base 10)
+ "|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)" + // (base 10, scientific notation without .)
+ "|[-+]?\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" + // (base 10, starting with .)
+ "|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*" + // (base 60)
+ "|[-+]?\\.(?:inf|Inf|INF)" + "|\\.(?:nan|NaN|NAN)" + ")$");
+ public static final Pattern INT = Pattern.compile("^(?:" + "[-+]?0b_*[0-1][0-1_]*" + // (base 2)
+ "|[-+]?0_*[0-7][0-7_]*" + // (base 8)
+ "|[-+]?(?:0|[1-9][0-9_]*)" + // (base 10)
+ "|[-+]?0x_*[0-9a-fA-F][0-9a-fA-F_]*" + // (base 16)
+ "|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+" + // (base 60)
+ ")$");
+ public static final Pattern MERGE = Pattern.compile("^(?:<<)$");
+ public static final Pattern NULL = Pattern.compile("^(?:~|null|Null|NULL| )$");
+ public static final Pattern EMPTY = Pattern.compile("^$");
+ public static final Pattern TIMESTAMP = Pattern.compile(
+ "^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?(?:[Tt]|[ \t]+)[0-9][0-9]?:[0-9][0-9]:[0-9][0-9](?:\\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$");
+ public static final Pattern VALUE = Pattern.compile("^(?:=)$");
+ public static final Pattern YAML = Pattern.compile("^(?:!|&|\\*)$");
- protected void addImplicitResolvers() {
- addImplicitResolver(Tag.BOOL, BOOL, "yYnNtTfFoO");
- /*
- * INT must be before FLOAT because the regular expression for FLOAT
- * matches INT (see issue 130)
- * http://code.google.com/p/snakeyaml/issues/detail?id=130
- */
- addImplicitResolver(Tag.INT, INT, "-+0123456789");
- addImplicitResolver(Tag.FLOAT, FLOAT, "-+0123456789.");
- addImplicitResolver(Tag.MERGE, MERGE, "<");
- addImplicitResolver(Tag.NULL, NULL, "~nN\0");
- addImplicitResolver(Tag.NULL, EMPTY, null);
- addImplicitResolver(Tag.TIMESTAMP, TIMESTAMP, "0123456789");
- // The following implicit resolver is only for documentation
- // purposes.
- // It cannot work
- // because plain scalars cannot start with '!', '&', or '*'.
- addImplicitResolver(Tag.YAML, YAML, "!&*");
- }
+ protected Map<Character, List<ResolverTuple>> yamlImplicitResolvers =
+ new HashMap<Character, List<ResolverTuple>>();
- public Resolver() {
- addImplicitResolvers();
- }
+ protected void addImplicitResolvers() {
+ addImplicitResolver(Tag.BOOL, BOOL, "yYnNtTfFoO", 10);
+ /*
+ * INT must be before FLOAT because the regular expression for FLOAT matches INT (see issue 130)
+ * http://code.google.com/p/snakeyaml/issues/detail?id=130
+ */
+ addImplicitResolver(Tag.INT, INT, "-+0123456789");
+ addImplicitResolver(Tag.FLOAT, FLOAT, "-+0123456789.");
+ addImplicitResolver(Tag.MERGE, MERGE, "<", 10);
+ addImplicitResolver(Tag.NULL, NULL, "~nN\0", 10);
+ addImplicitResolver(Tag.NULL, EMPTY, null, 10);
+ addImplicitResolver(Tag.TIMESTAMP, TIMESTAMP, "0123456789", 50);
+ // The following implicit resolver is only for documentation purposes.
+ // It cannot work because plain scalars cannot start with '!', '&', or '*'.
+ addImplicitResolver(Tag.YAML, YAML, "!&*", 10);
+ }
- public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
- if (first == null) {
- List<ResolverTuple> curr = yamlImplicitResolvers.get(null);
- if (curr == null) {
- curr = new ArrayList<ResolverTuple>();
- yamlImplicitResolvers.put(null, curr);
- }
- curr.add(new ResolverTuple(tag, regexp));
- } else {
- char[] chrs = first.toCharArray();
- for (int i = 0, j = chrs.length; i < j; i++) {
- Character theC = Character.valueOf(chrs[i]);
- if (theC == 0) {
- // special case: for null
- theC = null;
- }
- List<ResolverTuple> curr = yamlImplicitResolvers.get(theC);
- if (curr == null) {
- curr = new ArrayList<ResolverTuple>();
- yamlImplicitResolvers.put(theC, curr);
- }
- curr.add(new ResolverTuple(tag, regexp));
- }
+ public Resolver() {
+ addImplicitResolvers();
+ }
+
+ public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
+ addImplicitResolver(tag, regexp, first, 1024);
+ }
+
+ public void addImplicitResolver(Tag tag, Pattern regexp, String first, int limit) {
+ if (first == null) {
+ List<ResolverTuple> curr = yamlImplicitResolvers.get(null);
+ if (curr == null) {
+ curr = new ArrayList<ResolverTuple>();
+ yamlImplicitResolvers.put(null, curr);
+ }
+ curr.add(new ResolverTuple(tag, regexp, limit));
+ } else {
+ char[] chrs = first.toCharArray();
+ for (int i = 0, j = chrs.length; i < j; i++) {
+ Character theC = Character.valueOf(chrs[i]);
+ if (theC == 0) {
+ // special case: for null
+ theC = null;
}
+ List<ResolverTuple> curr = yamlImplicitResolvers.get(theC);
+ if (curr == null) {
+ curr = new ArrayList<ResolverTuple>();
+ yamlImplicitResolvers.put(theC, curr);
+ }
+ curr.add(new ResolverTuple(tag, regexp, limit));
+ }
}
+ }
- public Tag resolve(NodeId kind, String value, boolean implicit) {
- if (kind == NodeId.scalar && implicit) {
- List<ResolverTuple> resolvers = null;
- if (value.length() == 0) {
- resolvers = yamlImplicitResolvers.get('\0');
- } else {
- resolvers = yamlImplicitResolvers.get(value.charAt(0));
- }
- if (resolvers != null) {
- for (ResolverTuple v : resolvers) {
- Tag tag = v.getTag();
- Pattern regexp = v.getRegexp();
- if (regexp.matcher(value).matches()) {
- return tag;
- }
- }
- }
- if (yamlImplicitResolvers.containsKey(null)) {
- for (ResolverTuple v : yamlImplicitResolvers.get(null)) {
- Tag tag = v.getTag();
- Pattern regexp = v.getRegexp();
- if (regexp.matcher(value).matches()) {
- return tag;
- }
- }
- }
+ public Tag resolve(NodeId kind, String value, boolean implicit) {
+ if (kind == NodeId.scalar && implicit) {
+ final List<ResolverTuple> resolvers;
+ if (value.length() == 0) {
+ resolvers = yamlImplicitResolvers.get('\0');
+ } else {
+ resolvers = yamlImplicitResolvers.get(value.charAt(0));
+ }
+ if (resolvers != null) {
+ for (ResolverTuple v : resolvers) {
+ Tag tag = v.getTag();
+ Pattern regexp = v.getRegexp();
+ if (value.length() <= v.getLimit() && regexp.matcher(value).matches()) {
+ return tag;
+ }
}
- switch (kind) {
- case scalar:
- return Tag.STR;
- case sequence:
- return Tag.SEQ;
- default:
- return Tag.MAP;
+ }
+ if (yamlImplicitResolvers.containsKey(null)) {
+ // check null resolver
+ for (ResolverTuple v : yamlImplicitResolvers.get(null)) {
+ Tag tag = v.getTag();
+ Pattern regexp = v.getRegexp();
+ if (value.length() <= v.getLimit() && regexp.matcher(value).matches()) {
+ return tag;
+ }
}
+ }
+ }
+ switch (kind) {
+ case scalar:
+ return Tag.STR;
+ case sequence:
+ return Tag.SEQ;
+ default:
+ return Tag.MAP;
}
-} \ No newline at end of file
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/resolver/ResolverTuple.java b/src/main/java/org/yaml/snakeyaml/resolver/ResolverTuple.java
index 3fbfac04..0f8df776 100644
--- a/src/main/java/org/yaml/snakeyaml/resolver/ResolverTuple.java
+++ b/src/main/java/org/yaml/snakeyaml/resolver/ResolverTuple.java
@@ -1,43 +1,47 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.resolver;
import java.util.regex.Pattern;
-
import org.yaml.snakeyaml.nodes.Tag;
final class ResolverTuple {
- private final Tag tag;
- private final Pattern regexp;
-
- public ResolverTuple(Tag tag, Pattern regexp) {
- this.tag = tag;
- this.regexp = regexp;
- }
-
- public Tag getTag() {
- return tag;
- }
-
- public Pattern getRegexp() {
- return regexp;
- }
-
- @Override
- public String toString() {
- return "Tuple tag=" + tag + " regexp=" + regexp;
- }
-} \ No newline at end of file
+
+ private final Tag tag;
+ private final Pattern regexp;
+ private final int limit;
+
+ public ResolverTuple(Tag tag, Pattern regexp, int limit) {
+ this.tag = tag;
+ this.regexp = regexp;
+ this.limit = limit;
+ }
+
+ public Tag getTag() {
+ return tag;
+ }
+
+ public Pattern getRegexp() {
+ return regexp;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple tag=" + tag + " regexp=" + regexp + " limit=" + limit;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/scanner/Constant.java b/src/main/java/org/yaml/snakeyaml/scanner/Constant.java
index 391bcaa7..83720afe 100644
--- a/src/main/java/org/yaml/snakeyaml/scanner/Constant.java
+++ b/src/main/java/org/yaml/snakeyaml/scanner/Constant.java
@@ -1,76 +1,76 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.scanner;
import java.util.Arrays;
public final class Constant {
- private final static String ALPHA_S = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
- private final static String LINEBR_S = "\n\u0085\u2028\u2029";
- private final static String FULL_LINEBR_S = "\r" + LINEBR_S;
- private final static String NULL_OR_LINEBR_S = "\0" + FULL_LINEBR_S;
- private final static String NULL_BL_LINEBR_S = " " + NULL_OR_LINEBR_S;
- private final static String NULL_BL_T_LINEBR_S = "\t" + NULL_BL_LINEBR_S;
- private final static String NULL_BL_T_S = "\0 \t";
- private final static String URI_CHARS_S = ALPHA_S + "-;/?:@&=+$,_.!~*\'()[]%";
+ private static final String ALPHA_S =
+ "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
- public final static Constant LINEBR = new Constant(LINEBR_S);
- public final static Constant FULL_LINEBR = new Constant(FULL_LINEBR_S);
- public final static Constant NULL_OR_LINEBR = new Constant(NULL_OR_LINEBR_S);
- public final static Constant NULL_BL_LINEBR = new Constant(NULL_BL_LINEBR_S);
- public final static Constant NULL_BL_T_LINEBR = new Constant(NULL_BL_T_LINEBR_S);
- public final static Constant NULL_BL_T = new Constant(NULL_BL_T_S);
- public final static Constant URI_CHARS = new Constant(URI_CHARS_S);
+ private static final String LINEBR_S = "\n\u0085\u2028\u2029";
+ private static final String FULL_LINEBR_S = "\r" + LINEBR_S;
+ private static final String NULL_OR_LINEBR_S = "\0" + FULL_LINEBR_S;
+ private static final String NULL_BL_LINEBR_S = " " + NULL_OR_LINEBR_S;
+ private static final String NULL_BL_T_LINEBR_S = "\t" + NULL_BL_LINEBR_S;
+ private static final String NULL_BL_T_S = "\0 \t";
+ private static final String URI_CHARS_S = ALPHA_S + "-;/?:@&=+$,_.!~*'()[]%";
- public final static Constant ALPHA = new Constant(ALPHA_S);
+ public static final Constant LINEBR = new Constant(LINEBR_S);
+ public static final Constant NULL_OR_LINEBR = new Constant(NULL_OR_LINEBR_S);
+ public static final Constant NULL_BL_LINEBR = new Constant(NULL_BL_LINEBR_S);
+ public static final Constant NULL_BL_T_LINEBR = new Constant(NULL_BL_T_LINEBR_S);
+ public static final Constant NULL_BL_T = new Constant(NULL_BL_T_S);
+ public static final Constant URI_CHARS = new Constant(URI_CHARS_S);
- private String content;
- boolean[] contains = new boolean[128];
- boolean noASCII = false;
+ public static final Constant ALPHA = new Constant(ALPHA_S);
- private Constant(String content) {
- Arrays.fill(contains, false);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < content.length(); i++) {
- char ch = content.charAt(i);
- if (ch < 128)
- contains[ch] = true;
- else
- sb.append(ch);
- }
- if (sb.length() > 0) {
- noASCII = true;
- this.content = sb.toString();
- }
- }
+ private String content;
+ boolean[] contains = new boolean[128];
+ boolean noASCII = false;
- public boolean has(char ch) {
- return (ch < 128) ? contains[ch] : noASCII && content.indexOf(ch, 0) != -1;
+ private Constant(String content) {
+ Arrays.fill(contains, false);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < content.length(); i++) {
+ int c = content.codePointAt(i);
+ if (c < 128) {
+ contains[c] = true;
+ } else {
+ sb.appendCodePoint(c);
+ }
}
-
- public boolean hasNo(char ch) {
- return !has(ch);
+ if (sb.length() > 0) {
+ noASCII = true;
+ this.content = sb.toString();
}
+ }
- public boolean has(char ch, String additional) {
- return has(ch) || additional.indexOf(ch, 0) != -1;
- }
+ public boolean has(int c) {
+ return (c < 128) ? contains[c] : noASCII && content.indexOf(c) != -1;
+ }
- public boolean hasNo(char ch, String additional) {
- return !has(ch, additional);
- }
+ public boolean hasNo(int c) {
+ return !has(c);
+ }
+
+ public boolean has(int c, String additional) {
+ return has(c) || additional.indexOf(c) != -1;
+ }
+
+ public boolean hasNo(int c, String additional) {
+ return !has(c, additional);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/scanner/Scanner.java b/src/main/java/org/yaml/snakeyaml/scanner/Scanner.java
index 6fc0d97c..ef9b8d65 100644
--- a/src/main/java/org/yaml/snakeyaml/scanner/Scanner.java
+++ b/src/main/java/org/yaml/snakeyaml/scanner/Scanner.java
@@ -1,65 +1,60 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.scanner;
import org.yaml.snakeyaml.tokens.Token;
/**
- * This interface represents an input stream of {@link Token Tokens}.
+ * This interface represents an input stream of {@link Token}s.
* <p>
- * The parser and the scanner form together the 'Parse' step in the loading
- * process (see chapter 3.1 of the <a href="http://yaml.org/spec/1.1/">YAML
- * Specification</a>).
+ * The parser and the scanner form together the 'Parse' step in the loading process (see chapter 3.1
+ * of the <a href="http://yaml.org/spec/1.1/">YAML Specification</a>).
* </p>
- *
+ *
* @see org.yaml.snakeyaml.tokens.Token
*/
public interface Scanner {
- /**
- * Check if the next token is one of the given types.
- *
- * @param choices
- * token IDs.
- * @return <code>true</code> if the next token can be assigned to a variable
- * of at least one of the given types. Returns <code>false</code> if
- * no more tokens are available.
- * @throws ScannerException
- * Thrown in case of malformed input.
- */
- boolean checkToken(Token.ID... choices);
+ /**
+ * Check if the next token is one of the given types.
+ *
+ * @param choices token IDs to match with
+ * @return <code>true</code> if the next token is one of the given types. Returns
+ * <code>false</code> if no more tokens are available.
+ * @throws ScannerException Thrown in case of malformed input.
+ */
+ boolean checkToken(Token.ID... choices);
- /**
- * Return the next token, but do not delete it from the stream.
- *
- * @return The token that will be returned on the next call to
- * {@link #getToken}
- * @throws ScannerException
- * Thrown in case of malformed input.
- */
- Token peekToken();
+ /**
+ * Return the next token, but do not delete it from the stream.
+ *
+ * @return The token that will be returned on the next call to {@link #getToken}
+ * @throws ScannerException Thrown in case of malformed input.
+ * @throws IndexOutOfBoundsException if no more token left
+ */
+ Token peekToken();
- /**
- * Returns the next token.
- * <p>
- * The token will be removed from the stream.
- * </p>
- *
- * @throws ScannerException
- * Thrown in case of malformed input.
- */
- Token getToken();
+ /**
+ * Returns the next token.
+ * <p>
+ * The token will be removed from the stream. (Every invocation of this method must happen after
+ * calling either {@link #checkToken} or {@link #peekToken()}
+ * </p>
+ *
+ * @return the coming token
+ * @throws ScannerException Thrown in case of malformed input.
+ * @throws IndexOutOfBoundsException if no more token left
+ */
+ Token getToken();
}
diff --git a/src/main/java/org/yaml/snakeyaml/scanner/ScannerException.java b/src/main/java/org/yaml/snakeyaml/scanner/ScannerException.java
index b4ee9eef..1c3f5b51 100644
--- a/src/main/java/org/yaml/snakeyaml/scanner/ScannerException.java
+++ b/src/main/java/org/yaml/snakeyaml/scanner/ScannerException.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.scanner;
@@ -19,48 +17,35 @@ import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.MarkedYAMLException;
/**
- * Exception thrown by the {@link Scanner} implementations in case of malformed
- * input.
+ * Exception thrown by the {@link Scanner} implementations in case of malformed input.
*/
public class ScannerException extends MarkedYAMLException {
- private static final long serialVersionUID = 4782293188600445954L;
+ private static final long serialVersionUID = 4782293188600445954L;
- /**
- * Constructs an instance.
- *
- * @param context
- * Part of the input document in which vicinity the problem
- * occurred.
- * @param contextMark
- * Position of the <code>context</code> within the document.
- * @param problem
- * Part of the input document that caused the problem.
- * @param problemMark
- * Position of the <code>problem</code> within the document.
- * @param note
- * Message for the user with further information about the
- * problem.
- */
- public ScannerException(String context, Mark contextMark, String problem, Mark problemMark,
- String note) {
- super(context, contextMark, problem, problemMark, note);
- }
+ /**
+ * Constructs an instance.
+ *
+ * @param context Part of the input document in which vicinity the problem occurred.
+ * @param contextMark Position of the <code>context</code> within the document.
+ * @param problem Part of the input document that caused the problem.
+ * @param problemMark Position of the <code>problem</code> within the document.
+ * @param note Message for the user with further information about the problem.
+ */
+ public ScannerException(String context, Mark contextMark, String problem, Mark problemMark,
+ String note) {
+ super(context, contextMark, problem, problemMark, note);
+ }
- /**
- * Constructs an instance.
- *
- * @param context
- * Part of the input document in which vicinity the problem
- * occurred.
- * @param contextMark
- * Position of the <code>context</code> within the document.
- * @param problem
- * Part of the input document that caused the problem.
- * @param problemMark
- * Position of the <code>problem</code> within the document.
- */
- public ScannerException(String context, Mark contextMark, String problem, Mark problemMark) {
- this(context, contextMark, problem, problemMark, null);
- }
+ /**
+ * Constructs an instance.
+ *
+ * @param context Part of the input document in which vicinity the problem occurred.
+ * @param contextMark Position of the <code>context</code> within the document.
+ * @param problem Part of the input document that caused the problem.
+ * @param problemMark Position of the <code>problem</code> within the document.
+ */
+ public ScannerException(String context, Mark contextMark, String problem, Mark problemMark) {
+ this(context, contextMark, problem, problemMark, null);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java b/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java
index 4272aab7..ec8711f9 100644
--- a/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java
+++ b/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.scanner;
@@ -24,7 +22,9 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
-
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.reader.StreamReader;
@@ -34,6 +34,7 @@ import org.yaml.snakeyaml.tokens.BlockEndToken;
import org.yaml.snakeyaml.tokens.BlockEntryToken;
import org.yaml.snakeyaml.tokens.BlockMappingStartToken;
import org.yaml.snakeyaml.tokens.BlockSequenceStartToken;
+import org.yaml.snakeyaml.tokens.CommentToken;
import org.yaml.snakeyaml.tokens.DirectiveToken;
import org.yaml.snakeyaml.tokens.DocumentEndToken;
import org.yaml.snakeyaml.tokens.DocumentStartToken;
@@ -58,6 +59,7 @@ import org.yaml.snakeyaml.util.UriEncoder;
* Scanner produces tokens of the following types:
* STREAM-START
* STREAM-END
+ * COMMENT
* DIRECTIVE(name, value)
* DOCUMENT-START
* DOCUMENT-END
@@ -80,2209 +82,2311 @@ import org.yaml.snakeyaml.util.UriEncoder;
* </pre>
*/
public final class ScannerImpl implements Scanner {
- /**
- * A regular expression matching characters which are not in the hexadecimal
- * set (0-9, A-F, a-f).
- */
- private final static Pattern NOT_HEXA = Pattern.compile("[^0-9A-Fa-f]");
-
- /**
- * A mapping from an escaped character in the input stream to the character
- * that they should be replaced with.
- *
- * YAML defines several common and a few uncommon escape sequences.
- *
- * @see <a href="http://www.yaml.org/spec/current.html#id2517668">4.1.6.
- * Escape Sequences</a>
- */
- public final static Map<Character, String> ESCAPE_REPLACEMENTS = new HashMap<Character, String>();
-
- /**
- * A mapping from a character to a number of bytes to read-ahead for that
- * escape sequence. These escape sequences are used to handle unicode
- * escaping in the following formats, where H is a hexadecimal character:
- *
- * <pre>
- * &#92;xHH : escaped 8-bit Unicode character
- * &#92;uHHHH : escaped 16-bit Unicode character
- * &#92;UHHHHHHHH : escaped 32-bit Unicode character
- * </pre>
- *
- * @see <a href="http://yaml.org/spec/1.1/current.html#id872840">5.6. Escape
- * Sequences</a>
- */
- public final static Map<Character, Integer> ESCAPE_CODES = new HashMap<Character, Integer>();
-
- static {
- // ASCII null
- ESCAPE_REPLACEMENTS.put(Character.valueOf('0'), "\0");
- // ASCII bell
- ESCAPE_REPLACEMENTS.put(Character.valueOf('a'), "\u0007");
- // ASCII backspace
- ESCAPE_REPLACEMENTS.put(Character.valueOf('b'), "\u0008");
- // ASCII horizontal tab
- ESCAPE_REPLACEMENTS.put(Character.valueOf('t'), "\u0009");
- // ASCII newline (line feed; &#92;n maps to 0x0A)
- ESCAPE_REPLACEMENTS.put(Character.valueOf('n'), "\n");
- // ASCII vertical tab
- ESCAPE_REPLACEMENTS.put(Character.valueOf('v'), "\u000B");
- // ASCII form-feed
- ESCAPE_REPLACEMENTS.put(Character.valueOf('f'), "\u000C");
- // carriage-return (&#92;r maps to 0x0D)
- ESCAPE_REPLACEMENTS.put(Character.valueOf('r'), "\r");
- // ASCII escape character (Esc)
- ESCAPE_REPLACEMENTS.put(Character.valueOf('e'), "\u001B");
- // ASCII space
- ESCAPE_REPLACEMENTS.put(Character.valueOf(' '), "\u0020");
- // ASCII double-quote
- ESCAPE_REPLACEMENTS.put(Character.valueOf('"'), "\"");
- // ASCII backslash
- ESCAPE_REPLACEMENTS.put(Character.valueOf('\\'), "\\");
- // Unicode next line
- ESCAPE_REPLACEMENTS.put(Character.valueOf('N'), "\u0085");
- // Unicode non-breaking-space
- ESCAPE_REPLACEMENTS.put(Character.valueOf('_'), "\u00A0");
- // Unicode line-separator
- ESCAPE_REPLACEMENTS.put(Character.valueOf('L'), "\u2028");
- // Unicode paragraph separator
- ESCAPE_REPLACEMENTS.put(Character.valueOf('P'), "\u2029");
-
- // 8-bit Unicode
- ESCAPE_CODES.put(Character.valueOf('x'), 2);
- // 16-bit Unicode
- ESCAPE_CODES.put(Character.valueOf('u'), 4);
- // 32-bit Unicode (Supplementary characters are supported)
- ESCAPE_CODES.put(Character.valueOf('U'), 8);
- }
- private final StreamReader reader;
- // Had we reached the end of the stream?
- private boolean done = false;
-
- // The number of unclosed '{' and '['. `flow_level == 0` means block
- // context.
- private int flowLevel = 0;
-
- // List of processed tokens that are not yet emitted.
- private List<Token> tokens;
-
- // Number of tokens that were emitted through the `get_token` method.
- private int tokensTaken = 0;
-
- // The current indentation level.
- private int indent = -1;
-
- // Past indentation levels.
- private ArrayStack<Integer> indents;
-
- // Variables related to simple keys treatment. See PyYAML.
-
- /**
- * <pre>
- * A simple key is a key that is not denoted by the '?' indicator.
- * Example of simple keys:
- * ---
- * block simple key: value
- * ? not a simple key:
- * : { flow simple key: value }
- * We emit the KEY token before all keys, so when we find a potential
- * simple key, we try to locate the corresponding ':' indicator.
- * Simple keys should be limited to a single line and 1024 characters.
- *
- * Can a simple key start at the current position? A simple key may
- * start:
- * - at the beginning of the line, not counting indentation spaces
- * (in block context),
- * - after '{', '[', ',' (in the flow context),
- * - after '?', ':', '-' (in the block context).
- * In the block context, this flag also signifies if a block collection
- * may start at the current position.
- * </pre>
- */
- private boolean allowSimpleKey = true;
- /*
- * Keep track of possible simple keys. This is a dictionary. The key is
- * `flow_level`; there can be no more that one possible simple key for each
- * level. The value is a SimpleKey record: (token_number, required, index,
- * line, column, mark) A simple key may start with ALIAS, ANCHOR, TAG,
- * SCALAR(flow), '[', or '{' tokens.
- */
- private Map<Integer, SimpleKey> possibleSimpleKeys;
-
- public ScannerImpl(StreamReader reader) {
- this.reader = reader;
- this.tokens = new ArrayList<Token>(100);
- this.indents = new ArrayStack<Integer>(10);
- // The order in possibleSimpleKeys is kept for nextPossibleSimpleKey()
- this.possibleSimpleKeys = new LinkedHashMap<Integer, SimpleKey>();
- fetchStreamStart();// Add the STREAM-START token.
- }
-
- /**
- * Check whether the next token is one of the given types.
- */
- public boolean checkToken(Token.ID... choices) {
- while (needMoreTokens()) {
- fetchMoreTokens();
- }
- if (!this.tokens.isEmpty()) {
- if (choices.length == 0) {
- return true;
- }
- // since profiler puts this method on top (it is used a lot), we
- // should not use 'foreach' here because of the performance reasons
- Token.ID first = this.tokens.get(0).getTokenId();
- for (int i = 0; i < choices.length; i++) {
- if (first == choices[i]) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Return the next token, but do not delete it from the queue.
- */
- public Token peekToken() {
- while (needMoreTokens()) {
- fetchMoreTokens();
- }
- return this.tokens.get(0);
- }
-
- /**
- * Return the next token, removing it from the queue.
- */
- public Token getToken() {
- if (!this.tokens.isEmpty()) {
- this.tokensTaken++;
- return this.tokens.remove(0);
- }
- return null;
- }
-
- // Private methods.
- /**
- * Returns true if more tokens should be scanned.
- */
- private boolean needMoreTokens() {
- // If we are done, we do not require more tokens.
- if (this.done) {
- return false;
- }
- // If we aren't done, but we have no tokens, we need to scan more.
- if (this.tokens.isEmpty()) {
- return true;
- }
- // The current token may be a potential simple key, so we
- // need to look further.
- stalePossibleSimpleKeys();
- return nextPossibleSimpleKey() == this.tokensTaken;
- }
-
- /**
- * Fetch one or more tokens from the StreamReader.
- */
- private void fetchMoreTokens() {
- // Eat whitespaces and comments until we reach the next token.
- scanToNextToken();
- // Remove obsolete possible simple keys.
- stalePossibleSimpleKeys();
- // Compare the current indentation and column. It may add some tokens
- // and decrease the current indentation level.
- unwindIndent(reader.getColumn());
- // Peek the next character, to decide what the next group of tokens
- // will look like.
- char ch = reader.peek();
- switch (ch) {
- case '\0':
- // Is it the end of stream?
- fetchStreamEnd();
- return;
- case '%':
- // Is it a directive?
- if (checkDirective()) {
- fetchDirective();
- return;
- }
- break;
- case '-':
- // Is it the document start?
- if (checkDocumentStart()) {
- fetchDocumentStart();
- return;
- // Is it the block entry indicator?
- } else if (checkBlockEntry()) {
- fetchBlockEntry();
- return;
- }
- break;
- case '.':
- // Is it the document end?
- if (checkDocumentEnd()) {
- fetchDocumentEnd();
- return;
- }
- break;
- // TODO support for BOM within a stream. (not implemented in PyYAML)
- case '[':
- // Is it the flow sequence start indicator?
- fetchFlowSequenceStart();
- return;
- case '{':
- // Is it the flow mapping start indicator?
- fetchFlowMappingStart();
- return;
- case ']':
- // Is it the flow sequence end indicator?
- fetchFlowSequenceEnd();
- return;
- case '}':
- // Is it the flow mapping end indicator?
- fetchFlowMappingEnd();
- return;
- case ',':
- // Is it the flow entry indicator?
- fetchFlowEntry();
- return;
- // see block entry indicator above
- case '?':
- // Is it the key indicator?
- if (checkKey()) {
- fetchKey();
- return;
- }
- break;
- case ':':
- // Is it the value indicator?
- if (checkValue()) {
- fetchValue();
- return;
- }
- break;
- case '*':
- // Is it an alias?
- fetchAlias();
- return;
- case '&':
- // Is it an anchor?
- fetchAnchor();
- return;
- case '!':
- // Is it a tag?
- fetchTag();
- return;
- case '|':
- // Is it a literal scalar?
- if (this.flowLevel == 0) {
- fetchLiteral();
- return;
- }
- break;
- case '>':
- // Is it a folded scalar?
- if (this.flowLevel == 0) {
- fetchFolded();
- return;
- }
- break;
- case '\'':
- // Is it a single quoted scalar?
- fetchSingle();
- return;
- case '"':
- // Is it a double quoted scalar?
- fetchDouble();
- return;
- }
- // It must be a plain scalar then.
- if (checkPlain()) {
- fetchPlain();
- return;
- }
- // No? It's an error. Let's produce a nice error message.We do this by
- // converting escaped characters into their escape sequences. This is a
- // backwards use of the ESCAPE_REPLACEMENTS map.
- String chRepresentation = String.valueOf(ch);
- for (Character s : ESCAPE_REPLACEMENTS.keySet()) {
- String v = ESCAPE_REPLACEMENTS.get(s);
- if (v.equals(chRepresentation)) {
- chRepresentation = "\\" + s;// ' ' -> '\t'
- break;
- }
- }
- if (ch == '\t')
- chRepresentation += "(TAB)";
- String text = String
- .format("found character '%s' that cannot start any token. (Do not use %s for indentation)",
- chRepresentation, chRepresentation);
- throw new ScannerException("while scanning for the next token", null, text,
- reader.getMark());
- }
-
- // Simple keys treatment.
-
- /**
- * Return the number of the nearest possible simple key. Actually we don't
- * need to loop through the whole dictionary.
- */
- private int nextPossibleSimpleKey() {
- /*
- * the implementation is not as in PyYAML. Because
- * this.possibleSimpleKeys is ordered we can simply take the first key
- */
- if (!this.possibleSimpleKeys.isEmpty()) {
- return this.possibleSimpleKeys.values().iterator().next().getTokenNumber();
- }
- return -1;
- }
-
- /**
- * <pre>
- * Remove entries that are no longer possible simple keys. According to
- * the YAML specification, simple keys
- * - should be limited to a single line,
- * - should be no longer than 1024 characters.
- * Disabling this procedure will allow simple keys of any length and
- * height (may cause problems if indentation is broken though).
- * </pre>
- */
- private void stalePossibleSimpleKeys() {
- if (!this.possibleSimpleKeys.isEmpty()) {
- for (Iterator<SimpleKey> iterator = this.possibleSimpleKeys.values().iterator(); iterator
- .hasNext();) {
- SimpleKey key = iterator.next();
- if ((key.getLine() != reader.getLine())
- || (reader.getIndex() - key.getIndex() > 1024)) {
- // If the key is not on the same line as the current
- // position OR the difference in column between the token
- // start and the current position is more than the maximum
- // simple key length, then this cannot be a simple key.
- if (key.isRequired()) {
- // If the key was required, this implies an error
- // condition.
- throw new ScannerException("while scanning a simple key", key.getMark(),
- "could not find expected ':'", reader.getMark());
- }
- iterator.remove();
- }
- }
- }
- }
-
- /**
- * The next token may start a simple key. We check if it's possible and save
- * its position. This function is called for ALIAS, ANCHOR, TAG,
- * SCALAR(flow), '[', and '{'.
- */
- private void savePossibleSimpleKey() {
- // The next token may start a simple key. We check if it's possible
- // and save its position. This function is called for
- // ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.
-
- // Check if a simple key is required at the current position.
- // A simple key is required if this position is the root flowLevel, AND
- // the current indentation level is the same as the last indent-level.
- boolean required = (this.flowLevel == 0) && (this.indent == this.reader.getColumn());
-
- if (allowSimpleKey || !required) {
- // A simple key is required only if it is the first token in the
- // current line. Therefore it is always allowed.
- } else {
- throw new YAMLException(
- "A simple key is required only if it is the first token in the current line");
- }
-
- // The next token might be a simple key. Let's save it's number and
- // position.
- if (this.allowSimpleKey) {
- removePossibleSimpleKey();
- int tokenNumber = this.tokensTaken + this.tokens.size();
- SimpleKey key = new SimpleKey(tokenNumber, required, reader.getIndex(),
- reader.getLine(), this.reader.getColumn(), this.reader.getMark());
- this.possibleSimpleKeys.put(this.flowLevel, key);
+ /**
+ * A regular expression matching characters which are not in the hexadecimal set (0-9, A-F, a-f).
+ */
+ private static final Pattern NOT_HEXA = Pattern.compile("[^0-9A-Fa-f]");
+
+ /**
+ * A mapping from an escaped character in the input stream to the string representation that they
+ * should be replaced with.
+ *
+ * YAML defines several common and a few uncommon escape sequences.
+ *
+ * @see <a href="http://www.yaml.org/spec/current.html#id2517668">4.1.6. Escape Sequences</a>
+ */
+ public static final Map<Character, String> ESCAPE_REPLACEMENTS = new HashMap<Character, String>();
+
+ /**
+ * A mapping from a character to a number of bytes to read-ahead for that escape sequence. These
+ * escape sequences are used to handle unicode escaping in the following formats, where H is a
+ * hexadecimal character:
+ *
+ * <pre>
+ * &#92;xHH : escaped 8-bit Unicode character
+ * &#92;uHHHH : escaped 16-bit Unicode character
+ * &#92;UHHHHHHHH : escaped 32-bit Unicode character
+ * </pre>
+ *
+ * @see <a href="http://yaml.org/spec/1.1/current.html#id872840">5.6. Escape Sequences</a>
+ */
+ public static final Map<Character, Integer> ESCAPE_CODES = new HashMap<Character, Integer>();
+
+ static {
+ // ASCII null
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('0'), "\0");
+ // ASCII bell
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('a'), "\u0007");
+ // ASCII backspace
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('b'), "\u0008");
+ // ASCII horizontal tab
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('t'), "\u0009");
+ // ASCII newline (line feed; &#92;n maps to 0x0A)
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('n'), "\n");
+ // ASCII vertical tab
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('v'), "\u000B");
+ // ASCII form-feed
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('f'), "\u000C");
+ // carriage-return (&#92;r maps to 0x0D)
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('r'), "\r");
+ // ASCII escape character (Esc)
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('e'), "\u001B");
+ // ASCII space
+ ESCAPE_REPLACEMENTS.put(Character.valueOf(' '), "\u0020");
+ // ASCII double-quote
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('"'), "\"");
+ // ASCII backslash
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('\\'), "\\");
+ // Unicode next line
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('N'), "\u0085");
+ // Unicode non-breaking-space
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('_'), "\u00A0");
+ // Unicode line-separator
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('L'), "\u2028");
+ // Unicode paragraph separator
+ ESCAPE_REPLACEMENTS.put(Character.valueOf('P'), "\u2029");
+
+ // 8-bit Unicode
+ ESCAPE_CODES.put(Character.valueOf('x'), 2);
+ // 16-bit Unicode
+ ESCAPE_CODES.put(Character.valueOf('u'), 4);
+ // 32-bit Unicode (Supplementary characters are supported)
+ ESCAPE_CODES.put(Character.valueOf('U'), 8);
+ }
+
+ private final StreamReader reader;
+ // Had we reached the end of the stream?
+ private boolean done = false;
+
+ // The number of unclosed '{' and '['. `flow_level == 0` means block context.
+ private int flowLevel = 0;
+
+ // List of processed tokens that are not yet emitted.
+ private final List<Token> tokens;
+
+ // The last added token
+ private Token lastToken;
+
+ // Number of tokens that were emitted through the `getToken()` method.
+ private int tokensTaken = 0;
+
+ // The current indentation level.
+ private int indent = -1;
+
+ // Past indentation levels.
+ private final ArrayStack<Integer> indents;
+
+ // A flag that indicates if comments should be parsed
+ private boolean parseComments;
+
+ private final LoaderOptions loaderOptions;
+
+ // Variables related to simple keys treatment. See PyYAML.
+
+ /**
+ * <pre>
+ * A simple key is a key that is not denoted by the '?' indicator.
+ * Example of simple keys:
+ * ---
+ * block simple key: value
+ * ? not a simple key:
+ * : { flow simple key: value }
+ * We emit the KEY token before all keys, so when we find a potential
+ * simple key, we try to locate the corresponding ':' indicator.
+ * Simple keys should be limited to a single line and 1024 characters.
+ *
+ * Can a simple key start at the current position? A simple key may
+ * start:
+ * - at the beginning of the line, not counting indentation spaces
+ * (in block context),
+ * - after '{', '[', ',' (in the flow context),
+ * - after '?', ':', '-' (in the block context).
+ * In the block context, this flag also signifies if a block collection
+ * may start at the current position.
+ * </pre>
+ */
+ private boolean allowSimpleKey = true;
+
+ /*
+ * Keep track of possible simple keys. This is a dictionary. The key is `flow_level`; there can be
+ * no more than one possible simple key for each level. The value is a SimpleKey record:
+ * (token_number, required, index, line, column, mark) A simple key may start with ALIAS, ANCHOR,
+ * TAG, SCALAR(flow), '[', or '{' tokens.
+ */
+ private final Map<Integer, SimpleKey> possibleSimpleKeys;
+
+ public ScannerImpl(StreamReader reader) {
+ this(reader, new LoaderOptions());
+ }
+
+ public ScannerImpl(StreamReader reader, LoaderOptions options) {
+ this.parseComments = options.isProcessComments();
+ this.reader = reader;
+ this.tokens = new ArrayList<Token>(100);
+ this.indents = new ArrayStack<Integer>(10);
+ // The order in possibleSimpleKeys is kept for nextPossibleSimpleKey()
+ this.possibleSimpleKeys = new LinkedHashMap<Integer, SimpleKey>();
+ this.loaderOptions = options;
+ fetchStreamStart();// Add the STREAM-START token.
+ }
+
+ /**
+ * Please use LoaderOptions instead Set the scanner to ignore comments or parse them as a
+ * <code>CommentToken</code>.
+ *
+ * @param parseComments <code>true</code> to parse; <code>false</code> to ignore
+ */
+ @Deprecated
+ public ScannerImpl setParseComments(boolean parseComments) {
+ this.parseComments = parseComments;
+ return this;
+ }
+
+ @Deprecated
+ public boolean isParseComments() {
+ return parseComments;
+ }
+
+ /**
+ * Check whether the next token is one of the given types.
+ */
+ public boolean checkToken(Token.ID... choices) {
+ while (needMoreTokens()) {
+ fetchMoreTokens();
+ }
+ if (!this.tokens.isEmpty()) {
+ if (choices.length == 0) {
+ return true;
+ }
+ // since profiler puts this method on top (it is used a lot), we
+ // should not use 'foreach' here because of the performance reasons
+ Token.ID first = this.tokens.get(0).getTokenId();
+ for (int i = 0; i < choices.length; i++) {
+ if (first == choices[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the next token, but do not delete it from the queue.
+ */
+ public Token peekToken() {
+ while (needMoreTokens()) {
+ fetchMoreTokens();
+ }
+ return this.tokens.get(0);
+ }
+
+ /**
+ * Return the next token, removing it from the queue.
+ */
+ public Token getToken() {
+ this.tokensTaken++;
+ return this.tokens.remove(0);
+ }
+
+ // Private methods.
+
+ private void addToken(Token token) {
+ lastToken = token;
+ this.tokens.add(token);
+ }
+
+ private void addToken(int index, Token token) {
+ if (index == this.tokens.size()) {
+ lastToken = token;
+ }
+ this.tokens.add(index, token);
+ }
+
+ private void addAllTokens(List<Token> tokens) {
+ lastToken = tokens.get(tokens.size() - 1);
+ this.tokens.addAll(tokens);
+ }
+
+ /**
+ * Returns true if more tokens should be scanned.
+ */
+ private boolean needMoreTokens() {
+ // If we are done, we do not require more tokens.
+ if (this.done) {
+ return false;
+ }
+ // If we aren't done, but we have no tokens, we need to scan more.
+ if (this.tokens.isEmpty()) {
+ return true;
+ }
+ // The current token may be a potential simple key, so we
+ // need to look further.
+ stalePossibleSimpleKeys();
+ return nextPossibleSimpleKey() == this.tokensTaken;
+ }
+
+ /**
+ * Fetch one or more tokens from the StreamReader.
+ */
+ private void fetchMoreTokens() {
+ if (reader.getIndex() > loaderOptions.getCodePointLimit()) {
+ throw new YAMLException("The incoming YAML document exceeds the limit: "
+ + loaderOptions.getCodePointLimit() + " code points.");
+ }
+ // Eat whitespaces and process comments until we reach the next token.
+ scanToNextToken();
+ // Remove obsolete possible simple keys.
+ stalePossibleSimpleKeys();
+ // Compare the current indentation and column. It may add some tokens
+ // and decrease the current indentation level.
+ unwindIndent(reader.getColumn());
+ // Peek the next code point, to decide what the next group of tokens
+ // will look like.
+ int c = reader.peek();
+ switch (c) {
+ case '\0':
+ // Is it the end of stream?
+ fetchStreamEnd();
+ return;
+ case '%':
+ // Is it a directive?
+ if (checkDirective()) {
+ fetchDirective();
+ return;
+ }
+ break;
+ case '-':
+ // Is it the document start?
+ if (checkDocumentStart()) {
+ fetchDocumentStart();
+ return;
+ // Is it the block entry indicator?
+ } else if (checkBlockEntry()) {
+ fetchBlockEntry();
+ return;
+ }
+ break;
+ case '.':
+ // Is it the document end?
+ if (checkDocumentEnd()) {
+ fetchDocumentEnd();
+ return;
+ }
+ break;
+ // TODO support for BOM within a stream. (also not implemented in PyYAML)
+ case '[':
+ // Is it the flow sequence start indicator?
+ fetchFlowSequenceStart();
+ return;
+ case '{':
+ // Is it the flow mapping start indicator?
+ fetchFlowMappingStart();
+ return;
+ case ']':
+ // Is it the flow sequence end indicator?
+ fetchFlowSequenceEnd();
+ return;
+ case '}':
+ // Is it the flow mapping end indicator?
+ fetchFlowMappingEnd();
+ return;
+ case ',':
+ // Is it the flow entry indicator?
+ fetchFlowEntry();
+ return;
+ // see block entry indicator above
+ case '?':
+ // Is it the key indicator?
+ if (checkKey()) {
+ fetchKey();
+ return;
+ }
+ break;
+ case ':':
+ // Is it the value indicator?
+ if (checkValue()) {
+ fetchValue();
+ return;
+ }
+ break;
+ case '*':
+ // Is it an alias?
+ fetchAlias();
+ return;
+ case '&':
+ // Is it an anchor?
+ fetchAnchor();
+ return;
+ case '!':
+ // Is it a tag?
+ fetchTag();
+ return;
+ case '|':
+ // Is it a literal scalar?
+ if (this.flowLevel == 0) {
+ fetchLiteral();
+ return;
}
- }
-
- /**
- * Remove the saved possible key position at the current flow level.
- */
- private void removePossibleSimpleKey() {
- SimpleKey key = possibleSimpleKeys.remove(flowLevel);
- if (key != null && key.isRequired()) {
+ break;
+ case '>':
+ // Is it a folded scalar?
+ if (this.flowLevel == 0) {
+ fetchFolded();
+ return;
+ }
+ break;
+ case '\'':
+ // Is it a single quoted scalar?
+ fetchSingle();
+ return;
+ case '"':
+ // Is it a double quoted scalar?
+ fetchDouble();
+ return;
+ }
+ // It must be a plain scalar then.
+ if (checkPlain()) {
+ fetchPlain();
+ return;
+ }
+ // No? It's an error. Let's produce a nice error message.We do this by
+ // converting escaped characters into their escape sequences. This is a
+ // backwards use of the ESCAPE_REPLACEMENTS map.
+ String chRepresentation = escapeChar(String.valueOf(Character.toChars(c)));
+ if (c == '\t') {
+ chRepresentation += "(TAB)";
+ }
+ String text = String.format(
+ "found character '%s' that cannot start any token. (Do not use %s for indentation)",
+ chRepresentation, chRepresentation);
+ throw new ScannerException("while scanning for the next token", null, text, reader.getMark());
+ }
+
+ /**
+ * This is implemented in CharConstants in SnakeYAML Engine
+ */
+ private String escapeChar(String chRepresentation) {
+ for (Character s : ESCAPE_REPLACEMENTS.keySet()) {
+ String v = ESCAPE_REPLACEMENTS.get(s);
+ if (v.equals(chRepresentation)) {
+ return "\\" + s;// ' ' -> '\t'
+ }
+ }
+ return chRepresentation;
+ }
+
+ // Simple keys treatment.
+
+ /**
+ * Return the number of the nearest possible simple key. Actually we don't need to loop through
+ * the whole dictionary.
+ */
+ private int nextPossibleSimpleKey() {
+ /*
+ * the implementation is not as in PyYAML. Because this.possibleSimpleKeys is ordered we can
+ * simply take the first key
+ */
+ if (!this.possibleSimpleKeys.isEmpty()) {
+ return this.possibleSimpleKeys.values().iterator().next().getTokenNumber();
+ }
+ return -1;
+ }
+
+ /**
+ * <pre>
+ * Remove entries that are no longer possible simple keys. According to
+ * the YAML specification, simple keys
+ * - should be limited to a single line,
+ * - should be no longer than 1024 characters.
+ * Disabling this procedure will allow simple keys of any length and
+ * height (may cause problems if indentation is broken though).
+ * </pre>
+ */
+ private void stalePossibleSimpleKeys() {
+ if (!this.possibleSimpleKeys.isEmpty()) {
+ for (Iterator<SimpleKey> iterator = this.possibleSimpleKeys.values().iterator(); iterator
+ .hasNext();) {
+ SimpleKey key = iterator.next();
+ if ((key.getLine() != reader.getLine()) || (reader.getIndex() - key.getIndex() > 1024)) {
+ // If the key is not on the same line as the current
+ // position OR the difference in column between the token
+ // start and the current position is more than the maximum
+ // simple key length, then this cannot be a simple key.
+ if (key.isRequired()) {
+ // If the key was required, this implies an error
+ // condition.
throw new ScannerException("while scanning a simple key", key.getMark(),
- "could not find expected ':'", reader.getMark());
- }
- }
-
- // Indentation functions.
-
- /**
- * * Handle implicitly ending multiple levels of block nodes by decreased
- * indentation. This function becomes important on lines 4 and 7 of this
- * example:
- *
- * <pre>
- * 1) book one:
- * 2) part one:
- * 3) chapter one
- * 4) part two:
- * 5) chapter one
- * 6) chapter two
- * 7) book two:
- * </pre>
- *
- * In flow context, tokens should respect indentation. Actually the
- * condition should be `self.indent &gt;= column` according to the spec. But
- * this condition will prohibit intuitively correct constructions such as
- * key : { } </pre>
- */
- private void unwindIndent(int col) {
- // In the flow context, indentation is ignored. We make the scanner less
- // restrictive then specification requires.
- if (this.flowLevel != 0) {
- return;
- }
-
- // In block context, we may need to issue the BLOCK-END tokens.
- while (this.indent > col) {
- Mark mark = reader.getMark();
- this.indent = this.indents.pop();
- this.tokens.add(new BlockEndToken(mark, mark));
- }
- }
-
- /**
- * Check if we need to increase indentation.
- */
- private boolean addIndent(int column) {
- if (this.indent < column) {
- this.indents.push(this.indent);
- this.indent = column;
- return true;
- }
- return false;
- }
-
- // Fetchers.
-
- /**
- * We always add STREAM-START as the first token and STREAM-END as the last
- * token.
- */
- private void fetchStreamStart() {
- // Read the token.
+ "could not find expected ':'", reader.getMark());
+ }
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * The next token may start a simple key. We check if it's possible and save its position. This
+ * function is called for ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.
+ */
+ private void savePossibleSimpleKey() {
+ // The next token may start a simple key. We check if it's possible
+ // and save its position. This function is called for
+ // ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.
+
+ // Check if a simple key is required at the current position.
+ // A simple key is required if this position is the root flowLevel, AND
+ // the current indentation level is the same as the last indent-level.
+ boolean required = (this.flowLevel == 0) && (this.indent == this.reader.getColumn());
+
+ if (allowSimpleKey || !required) {
+ // A simple key is required only if it is the first token in the
+ // current line. Therefore it is always allowed.
+ } else {
+ throw new YAMLException(
+ "A simple key is required only if it is the first token in the current line");
+ }
+
+ // The next token might be a simple key. Let's save it's number and
+ // position.
+ if (this.allowSimpleKey) {
+ removePossibleSimpleKey();
+ int tokenNumber = this.tokensTaken + this.tokens.size();
+ SimpleKey key = new SimpleKey(tokenNumber, required, reader.getIndex(), reader.getLine(),
+ this.reader.getColumn(), this.reader.getMark());
+ this.possibleSimpleKeys.put(this.flowLevel, key);
+ }
+ }
+
+ /**
+ * Remove the saved possible key position at the current flow level.
+ */
+ private void removePossibleSimpleKey() {
+ SimpleKey key = possibleSimpleKeys.remove(flowLevel);
+ if (key != null && key.isRequired()) {
+ throw new ScannerException("while scanning a simple key", key.getMark(),
+ "could not find expected ':'", reader.getMark());
+ }
+ }
+
+ // Indentation functions.
+
+ /**
+ * * Handle implicitly ending multiple levels of block nodes by decreased indentation. This
+ * function becomes important on lines 4 and 7 of this example:
+ *
+ * <pre>
+ * 1) book one:
+ * 2) part one:
+ * 3) chapter one
+ * 4) part two:
+ * 5) chapter one
+ * 6) chapter two
+ * 7) book two:
+ * </pre>
+ *
+ * In flow context, tokens should respect indentation. Actually the condition should be
+ * `self.indent &gt;= column` according to the spec. But this condition will prohibit intuitively
+ * correct constructions such as key : { }
+ * </pre>
+ */
+ private void unwindIndent(int col) {
+ // In the flow context, indentation is ignored. We make the scanner less
+ // restrictive than specification requires.
+ if (this.flowLevel != 0) {
+ return;
+ }
+
+ // In block context, we may need to issue the BLOCK-END tokens.
+ while (this.indent > col) {
+ Mark mark = reader.getMark();
+ this.indent = this.indents.pop();
+ addToken(new BlockEndToken(mark, mark));
+ }
+ }
+
+ /**
+ * Check if we need to increase indentation.
+ */
+ private boolean addIndent(int column) {
+ if (this.indent < column) {
+ this.indents.push(this.indent);
+ this.indent = column;
+ return true;
+ }
+ return false;
+ }
+
+ // Fetchers.
+
+ /**
+ * We always add STREAM-START as the first token and STREAM-END as the last token.
+ */
+ private void fetchStreamStart() {
+ // Read the token.
+ Mark mark = reader.getMark();
+
+ // Add STREAM-START.
+ Token token = new StreamStartToken(mark, mark);
+ addToken(token);
+ }
+
+ private void fetchStreamEnd() {
+ // Set the current indentation to -1.
+ unwindIndent(-1);
+
+ // Reset simple keys.
+ removePossibleSimpleKey();
+ this.allowSimpleKey = false;
+ this.possibleSimpleKeys.clear();
+
+ // Read the token.
+ Mark mark = reader.getMark();
+
+ // Add STREAM-END.
+ Token token = new StreamEndToken(mark, mark);
+ addToken(token);
+
+ // The stream is finished.
+ this.done = true;
+ }
+
+ /**
+ * Fetch a YAML directive. Directives are presentation details that are interpreted as
+ * instructions to the processor. YAML defines two kinds of directives, YAML and TAG; all other
+ * types are reserved for future use.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id864824">3.2.3.4. Directives</a>
+ */
+ private void fetchDirective() {
+ // Set the current indentation to -1.
+ unwindIndent(-1);
+
+ // Reset simple keys.
+ removePossibleSimpleKey();
+ this.allowSimpleKey = false;
+
+ // Scan and add DIRECTIVE.
+ List<Token> tok = scanDirective();
+ addAllTokens(tok);
+ }
+
+ /**
+ * Fetch a document-start token ("---").
+ */
+ private void fetchDocumentStart() {
+ fetchDocumentIndicator(true);
+ }
+
+ /**
+ * Fetch a document-end token ("...").
+ */
+ private void fetchDocumentEnd() {
+ fetchDocumentIndicator(false);
+ }
+
+ /**
+ * Fetch a document indicator, either "---" for "document-start", or else "..." for "document-end.
+ * The type is chosen by the given boolean.
+ */
+ private void fetchDocumentIndicator(boolean isDocumentStart) {
+ // Set the current indentation to -1.
+ unwindIndent(-1);
+
+ // Reset simple keys. Note that there could not be a block collection
+ // after '---'.
+ removePossibleSimpleKey();
+ this.allowSimpleKey = false;
+
+ // Add DOCUMENT-START or DOCUMENT-END.
+ Mark startMark = reader.getMark();
+ reader.forward(3);
+ Mark endMark = reader.getMark();
+ Token token;
+ if (isDocumentStart) {
+ token = new DocumentStartToken(startMark, endMark);
+ } else {
+ token = new DocumentEndToken(startMark, endMark);
+ }
+ addToken(token);
+ }
+
+ private void fetchFlowSequenceStart() {
+ fetchFlowCollectionStart(false);
+ }
+
+ private void fetchFlowMappingStart() {
+ fetchFlowCollectionStart(true);
+ }
+
+ /**
+ * Fetch a flow-style collection start, which is either a sequence or a mapping. The type is
+ * determined by the given boolean.
+ *
+ * A flow-style collection is in a format similar to JSON. Sequences are started by '[' and ended
+ * by ']'; mappings are started by '{' and ended by '}'.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ *
+ * @param isMappingStart
+ */
+ private void fetchFlowCollectionStart(boolean isMappingStart) {
+ // '[' and '{' may start a simple key.
+ savePossibleSimpleKey();
+
+ // Increase the flow level.
+ this.flowLevel++;
+
+ // Simple keys are allowed after '[' and '{'.
+ this.allowSimpleKey = true;
+
+ // Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
+ Mark startMark = reader.getMark();
+ reader.forward(1);
+ Mark endMark = reader.getMark();
+ Token token;
+ if (isMappingStart) {
+ token = new FlowMappingStartToken(startMark, endMark);
+ } else {
+ token = new FlowSequenceStartToken(startMark, endMark);
+ }
+ addToken(token);
+ }
+
+ private void fetchFlowSequenceEnd() {
+ fetchFlowCollectionEnd(false);
+ }
+
+ private void fetchFlowMappingEnd() {
+ fetchFlowCollectionEnd(true);
+ }
+
+ /**
+ * Fetch a flow-style collection end, which is either a sequence or a mapping. The type is
+ * determined by the given boolean.
+ *
+ * A flow-style collection is in a format similar to JSON. Sequences are started by '[' and ended
+ * by ']'; mappings are started by '{' and ended by '}'.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchFlowCollectionEnd(boolean isMappingEnd) {
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+
+ // Decrease the flow level.
+ this.flowLevel--;
+
+ // No simple keys after ']' or '}'.
+ this.allowSimpleKey = false;
+
+ // Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ Mark endMark = reader.getMark();
+ Token token;
+ if (isMappingEnd) {
+ token = new FlowMappingEndToken(startMark, endMark);
+ } else {
+ token = new FlowSequenceEndToken(startMark, endMark);
+ }
+ addToken(token);
+ }
+
+ /**
+ * Fetch an entry in the flow style. Flow-style entries occur either immediately after the start
+ * of a collection, or else after a comma.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchFlowEntry() {
+ // Simple keys are allowed after ','.
+ this.allowSimpleKey = true;
+
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+
+ // Add FLOW-ENTRY.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ Mark endMark = reader.getMark();
+ Token token = new FlowEntryToken(startMark, endMark);
+ addToken(token);
+ }
+
+ /**
+ * Fetch an entry in the block style.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchBlockEntry() {
+ // Block context needs additional checks.
+ if (this.flowLevel == 0) {
+ // Are we allowed to start a new entry?
+ if (!this.allowSimpleKey) {
+ throw new ScannerException(null, null, "sequence entries are not allowed here",
+ reader.getMark());
+ }
+
+ // We may need to add BLOCK-SEQUENCE-START.
+ if (addIndent(this.reader.getColumn())) {
Mark mark = reader.getMark();
-
- // Add STREAM-START.
- Token token = new StreamStartToken(mark, mark);
- this.tokens.add(token);
- }
-
- private void fetchStreamEnd() {
- // Set the current intendation to -1.
- unwindIndent(-1);
-
- // Reset simple keys.
- removePossibleSimpleKey();
- this.allowSimpleKey = false;
- this.possibleSimpleKeys.clear();
-
- // Read the token.
+ addToken(new BlockSequenceStartToken(mark, mark));
+ }
+ } else {
+ // It's an error for the block entry to occur in the flow
+ // context,but we let the parser detect this.
+ }
+ // Simple keys are allowed after '-'.
+ this.allowSimpleKey = true;
+
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+
+ // Add BLOCK-ENTRY.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ Mark endMark = reader.getMark();
+ Token token = new BlockEntryToken(startMark, endMark);
+ addToken(token);
+ }
+
+ /**
+ * Fetch a key in a block-style mapping.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchKey() {
+ // Block context needs additional checks.
+ if (this.flowLevel == 0) {
+ // Are we allowed to start a key (not necessary a simple)?
+ if (!this.allowSimpleKey) {
+ throw new ScannerException(null, null, "mapping keys are not allowed here",
+ reader.getMark());
+ }
+ // We may need to add BLOCK-MAPPING-START.
+ if (addIndent(this.reader.getColumn())) {
Mark mark = reader.getMark();
-
- // Add STREAM-END.
- Token token = new StreamEndToken(mark, mark);
- this.tokens.add(token);
-
- // The stream is finished.
- this.done = true;
- }
-
- /**
- * Fetch a YAML directive. Directives are presentation details that are
- * interpreted as instructions to the processor. YAML defines two kinds of
- * directives, YAML and TAG; all other types are reserved for future use.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id864824"></a>
- */
- private void fetchDirective() {
- // Set the current intendation to -1.
- unwindIndent(-1);
-
- // Reset simple keys.
- removePossibleSimpleKey();
- this.allowSimpleKey = false;
-
- // Scan and add DIRECTIVE.
- Token tok = scanDirective();
- this.tokens.add(tok);
- }
-
- /**
- * Fetch a document-start token ("---").
- */
- private void fetchDocumentStart() {
- fetchDocumentIndicator(true);
- }
-
- /**
- * Fetch a document-end token ("...").
- */
- private void fetchDocumentEnd() {
- fetchDocumentIndicator(false);
- }
-
- /**
- * Fetch a document indicator, either "---" for "document-start", or else
- * "..." for "document-end. The type is chosen by the given boolean.
- */
- private void fetchDocumentIndicator(boolean isDocumentStart) {
- // Set the current intendation to -1.
- unwindIndent(-1);
-
- // Reset simple keys. Note that there could not be a block collection
- // after '---'.
- removePossibleSimpleKey();
- this.allowSimpleKey = false;
-
- // Add DOCUMENT-START or DOCUMENT-END.
- Mark startMark = reader.getMark();
- reader.forward(3);
- Mark endMark = reader.getMark();
- Token token;
- if (isDocumentStart) {
- token = new DocumentStartToken(startMark, endMark);
- } else {
- token = new DocumentEndToken(startMark, endMark);
- }
- this.tokens.add(token);
- }
-
- private void fetchFlowSequenceStart() {
- fetchFlowCollectionStart(false);
- }
-
- private void fetchFlowMappingStart() {
- fetchFlowCollectionStart(true);
- }
-
+ addToken(new BlockMappingStartToken(mark, mark));
+ }
+ }
+ // Simple keys are allowed after '?' in the block context.
+ this.allowSimpleKey = this.flowLevel == 0;
+
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+
+ // Add KEY.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ Mark endMark = reader.getMark();
+ Token token = new KeyToken(startMark, endMark);
+ addToken(token);
+ }
+
+ /**
+ * Fetch a value in a block-style mapping.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchValue() {
+ // Do we determine a simple key?
+ SimpleKey key = this.possibleSimpleKeys.remove(this.flowLevel);
+ if (key != null) {
+ // Add KEY.
+ addToken(key.getTokenNumber() - this.tokensTaken, new KeyToken(key.getMark(), key.getMark()));
+
+ // If this key starts a new block mapping, we need to add
+ // BLOCK-MAPPING-START.
+ if (this.flowLevel == 0) {
+ if (addIndent(key.getColumn())) {
+ addToken(key.getTokenNumber() - this.tokensTaken,
+ new BlockMappingStartToken(key.getMark(), key.getMark()));
+ }
+ }
+ // There cannot be two simple keys one after another.
+ this.allowSimpleKey = false;
+
+ } else {
+ // It must be a part of a complex key.
+ // Block context needs additional checks. Do we really need them?
+ // They will be caught by the parser anyway.
+ if (this.flowLevel == 0) {
+
+ // We are allowed to start a complex value if and only if we can
+ // start a simple key.
+ if (!this.allowSimpleKey) {
+ throw new ScannerException(null, null, "mapping values are not allowed here",
+ reader.getMark());
+ }
+ }
+
+ // If this value starts a new block mapping, we need to add
+ // BLOCK-MAPPING-START. It will be detected as an error later by
+ // the parser.
+ if (flowLevel == 0) {
+ if (addIndent(reader.getColumn())) {
+ Mark mark = reader.getMark();
+ addToken(new BlockMappingStartToken(mark, mark));
+ }
+ }
+
+ // Simple keys are allowed after ':' in the block context.
+ allowSimpleKey = flowLevel == 0;
+
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+ }
+ // Add VALUE.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ Mark endMark = reader.getMark();
+ Token token = new ValueToken(startMark, endMark);
+ addToken(token);
+ }
+
+ /**
+ * Fetch an alias, which is a reference to an anchor. Aliases take the format:
+ *
+ * <pre>
+ * *(anchor name)
+ * </pre>
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863390">3.2.2.2. Anchors and Aliases</a>
+ */
+ private void fetchAlias() {
+ // ALIAS could be a simple key.
+ savePossibleSimpleKey();
+
+ // No simple keys after ALIAS.
+ this.allowSimpleKey = false;
+
+ // Scan and add ALIAS.
+ Token tok = scanAnchor(false);
+ addToken(tok);
+ }
+
+ /**
+ * Fetch an anchor. Anchors take the form:
+ *
+ * <pre>
+ * &(anchor name)
+ * </pre>
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863390">3.2.2.2. Anchors and Aliases</a>
+ */
+ private void fetchAnchor() {
+ // ANCHOR could start a simple key.
+ savePossibleSimpleKey();
+
+ // No simple keys after ANCHOR.
+ this.allowSimpleKey = false;
+
+ // Scan and add ANCHOR.
+ Token tok = scanAnchor(true);
+ addToken(tok);
+ }
+
+ /**
+ * Fetch a tag. Tags take a complex form.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id861700">3.2.1.2. Tags</a>
+ */
+ private void fetchTag() {
+ // TAG could start a simple key.
+ savePossibleSimpleKey();
+
+ // No simple keys after TAG.
+ this.allowSimpleKey = false;
+
+ // Scan and add TAG.
+ Token tok = scanTag();
+ addToken(tok);
+ }
+
+ /**
+ * Fetch a literal scalar, denoted with a vertical-bar. This is the type best used for source code
+ * and other content, such as binary data, which must be included verbatim.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchLiteral() {
+ fetchBlockScalar('|');
+ }
+
+ /**
+ * Fetch a folded scalar, denoted with a greater-than sign. This is the type best used for long
+ * content, such as the text of a chapter or description.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ */
+ private void fetchFolded() {
+ fetchBlockScalar('>');
+ }
+
+ /**
+ * Fetch a block scalar (literal or folded).
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ *
+ * @param style
+ */
+ private void fetchBlockScalar(char style) {
+ // A simple key may follow a block scalar.
+ this.allowSimpleKey = true;
+
+ // Reset possible simple key on the current level.
+ removePossibleSimpleKey();
+
+ // Scan and add SCALAR.
+ List<Token> tok = scanBlockScalar(style);
+ addAllTokens(tok);
+ }
+
+ /**
+ * Fetch a single-quoted (') scalar.
+ */
+ private void fetchSingle() {
+ fetchFlowScalar('\'');
+ }
+
+ /**
+ * Fetch a double-quoted (") scalar.
+ */
+ private void fetchDouble() {
+ fetchFlowScalar('"');
+ }
+
+ /**
+ * Fetch a flow scalar (single- or double-quoted).
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id863975">3.2.3.1. Node Styles</a>
+ *
+ * @param style
+ */
+ private void fetchFlowScalar(char style) {
+ // A flow scalar could be a simple key.
+ savePossibleSimpleKey();
+
+ // No simple keys after flow scalars.
+ this.allowSimpleKey = false;
+
+ // Scan and add SCALAR.
+ Token tok = scanFlowScalar(style);
+ addToken(tok);
+ }
+
+ /**
+ * Fetch a plain scalar.
+ */
+ private void fetchPlain() {
+ // A plain scalar could be a simple key.
+ savePossibleSimpleKey();
+
+ // No simple keys after plain scalars. But note that `scan_plain` will
+ // change this flag if the scan is finished at the beginning of the
+ // line.
+ this.allowSimpleKey = false;
+
+ // Scan and add SCALAR. May change `allow_simple_key`.
+ Token tok = scanPlain();
+ addToken(tok);
+ }
+
+ // Checkers.
+
+ /**
+ * Returns true if the next thing on the reader is a directive, given that the leading '%' has
+ * already been checked.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id864824">3.2.3.4. Directives</a>
+ */
+ private boolean checkDirective() {
+ // DIRECTIVE: ^ '%' ...
+ // The '%' indicator is already checked.
+ return reader.getColumn() == 0;
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a document-start ("---"). A document-start is
+ * always followed immediately by a new line.
+ */
+ private boolean checkDocumentStart() {
+ // DOCUMENT-START: ^ '---' (' '|'\n')
+ if (reader.getColumn() == 0) {
+ return "---".equals(reader.prefix(3)) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3));
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a document-end ("..."). A document-end is
+ * always followed immediately by a new line.
+ */
+ private boolean checkDocumentEnd() {
+ // DOCUMENT-END: ^ '...' (' '|'\n')
+ if (reader.getColumn() == 0) {
+ return "...".equals(reader.prefix(3)) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3));
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a block token.
+ */
+ private boolean checkBlockEntry() {
+ // BLOCK-ENTRY: '-' (' '|'\n')
+ return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a key token.
+ */
+ private boolean checkKey() {
+ // KEY(flow context): '?'
+ if (this.flowLevel != 0) {
+ return true;
+ } else {
+ // KEY(block context): '?' (' '|'\n')
+ return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
+ }
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a value token.
+ */
+ private boolean checkValue() {
+ // VALUE(flow context): ':'
+ if (flowLevel != 0) {
+ return true;
+ } else {
+ // VALUE(block context): ':' (' '|'\n')
+ return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
+ }
+ }
+
+ /**
+ * Returns true if the next thing on the reader is a plain token.
+ */
+ private boolean checkPlain() {
/**
- * Fetch a flow-style collection start, which is either a sequence or a
- * mapping. The type is determined by the given boolean.
- *
- * A flow-style collection is in a format similar to JSON. Sequences are
- * started by '[' and ended by ']'; mappings are started by '{' and ended by
- * '}'.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- *
- * @param isMappingStart
+ * <pre>
+ * A plain scalar may start with any non-space character except:
+ * '-', '?', ':', ',', '[', ']', '{', '}',
+ * '#', '&amp;', '*', '!', '|', '&gt;', '\'', '\&quot;',
+ * '%', '@', '`'.
+ *
+ * It may also start with
+ * '-', '?', ':'
+ * if it is followed by a non-space character.
+ *
+ * Note that we limit the last rule to the block context (except the
+ * '-' character) because we want the flow context to be space
+ * independent.
+ * </pre>
*/
- private void fetchFlowCollectionStart(boolean isMappingStart) {
- // '[' and '{' may start a simple key.
- savePossibleSimpleKey();
-
- // Increase the flow level.
- this.flowLevel++;
-
- // Simple keys are allowed after '[' and '{'.
- this.allowSimpleKey = true;
-
- // Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
- Mark startMark = reader.getMark();
- reader.forward(1);
- Mark endMark = reader.getMark();
- Token token;
- if (isMappingStart) {
- token = new FlowMappingStartToken(startMark, endMark);
+ int c = reader.peek();
+ // If the next char is NOT one of the forbidden chars above or
+ // whitespace, then this is the start of a plain scalar.
+ return Constant.NULL_BL_T_LINEBR.hasNo(c, "-?:,[]{}#&*!|>'\"%@`")
+ || (Constant.NULL_BL_T_LINEBR.hasNo(reader.peek(1))
+ && (c == '-' || (this.flowLevel == 0 && "?:".indexOf(c) != -1)));
+ }
+
+ // Scanners.
+
+ /**
+ * <pre>
+ * We ignore spaces, line breaks and comments.
+ * If we find a line break in the block context, we set the flag
+ * `allow_simple_key` on.
+ * The byte order mark is stripped if it's the first character in the
+ * stream. We do not yet support BOM inside the stream as the
+ * specification requires. Any such mark will be considered as a part
+ * of the document.
+ * TODO: We need to make tab handling rules more sane. A good rule is
+ * Tabs cannot precede tokens
+ * BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
+ * KEY(block), VALUE(block), BLOCK-ENTRY
+ * So the checking code is
+ * if &lt;TAB&gt;:
+ * self.allow_simple_keys = False
+ * We also need to add the check for `allow_simple_keys == True` to
+ * `unwind_indent` before issuing BLOCK-END.
+ * Scanners for block, flow, and plain scalars need to be modified.
+ * </pre>
+ */
+ private void scanToNextToken() {
+ // If there is a byte order mark (BOM) at the beginning of the stream,
+ // forward past it.
+ if (reader.getIndex() == 0 && reader.peek() == 0xFEFF) {
+ reader.forward();
+ }
+ boolean found = false;
+ int inlineStartColumn = -1;
+ while (!found) {
+ Mark startMark = reader.getMark();
+ int columnBeforeComment = reader.getColumn();
+ boolean commentSeen = false;
+ int ff = 0;
+ // Peek ahead until we find the first non-space character, then
+ // move forward directly to that character.
+ while (reader.peek(ff) == ' ') {
+ ff++;
+ }
+ if (ff > 0) {
+ reader.forward(ff);
+ }
+ // If the character we have skipped forward to is a comment (#),
+ // then peek ahead until we find the next end of line. YAML
+ // comments are from a # to the next new-line. We then forward
+ // past the comment.
+ if (reader.peek() == '#') {
+ commentSeen = true;
+ CommentType type;
+ if (columnBeforeComment != 0
+ && !(lastToken != null && lastToken.getTokenId() == Token.ID.BlockEntry)) {
+ type = CommentType.IN_LINE;
+ inlineStartColumn = reader.getColumn();
+ } else if (inlineStartColumn == reader.getColumn()) {
+ type = CommentType.IN_LINE;
} else {
- token = new FlowSequenceStartToken(startMark, endMark);
+ inlineStartColumn = -1;
+ type = CommentType.BLOCK;
+ }
+ CommentToken token = scanComment(type);
+ if (parseComments) {
+ addToken(token);
+ }
+ }
+ // If we scanned a line break, then (depending on flow level),
+ // simple keys may be allowed.
+ String breaks = scanLineBreak();
+ if (breaks.length() != 0) {// found a line-break
+ if (parseComments && !commentSeen) {
+ if (columnBeforeComment == 0) {
+ Mark endMark = reader.getMark();
+ addToken(new CommentToken(CommentType.BLANK_LINE, breaks, startMark, endMark));
+ }
}
- this.tokens.add(token);
- }
-
- private void fetchFlowSequenceEnd() {
- fetchFlowCollectionEnd(false);
- }
-
- private void fetchFlowMappingEnd() {
- fetchFlowCollectionEnd(true);
- }
-
- /**
- * Fetch a flow-style collection end, which is either a sequence or a
- * mapping. The type is determined by the given boolean.
- *
- * A flow-style collection is in a format similar to JSON. Sequences are
- * started by '[' and ended by ']'; mappings are started by '{' and ended by
- * '}'.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchFlowCollectionEnd(boolean isMappingEnd) {
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
-
- // Decrease the flow level.
- this.flowLevel--;
-
- // No simple keys after ']' or '}'.
- this.allowSimpleKey = false;
-
- // Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
- Mark startMark = reader.getMark();
+ if (this.flowLevel == 0) {
+ // Simple keys are allowed at flow-level 0 after a line
+ // break
+ this.allowSimpleKey = true;
+ }
+ } else {
+ found = true;
+ }
+ }
+ }
+
+ private CommentToken scanComment(CommentType type) {
+ // See the specification for details.
+ Mark startMark = reader.getMark();
+ reader.forward();
+ int length = 0;
+ while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(length))) {
+ length++;
+ }
+ String value = reader.prefixForward(length);
+ Mark endMark = reader.getMark();
+ return new CommentToken(type, value, startMark, endMark);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private List<Token> scanDirective() {
+ // See the specification for details.
+ Mark startMark = reader.getMark();
+ Mark endMark;
+ reader.forward();
+ String name = scanDirectiveName(startMark);
+ List<?> value = null;
+ if ("YAML".equals(name)) {
+ value = scanYamlDirectiveValue(startMark);
+ endMark = reader.getMark();
+ } else if ("TAG".equals(name)) {
+ value = scanTagDirectiveValue(startMark);
+ endMark = reader.getMark();
+ } else {
+ endMark = reader.getMark();
+ int ff = 0;
+ while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(ff))) {
+ ff++;
+ }
+ if (ff > 0) {
+ reader.forward(ff);
+ }
+ }
+ CommentToken commentToken = scanDirectiveIgnoredLine(startMark);
+ DirectiveToken token = new DirectiveToken(name, value, startMark, endMark);
+ return makeTokenList(token, commentToken);
+ }
+
+ /**
+ * Scan a directive name. Directive names are a series of non-space characters.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id895217">7.1. Directives</a>
+ */
+ private String scanDirectiveName(Mark startMark) {
+ // See the specification for details.
+ int length = 0;
+ // A Directive-name is a sequence of alphanumeric characters
+ // (a-z,A-Z,0-9). We scan until we find something that isn't.
+ // FIXME this disagrees with the specification.
+ int c = reader.peek(length);
+ while (Constant.ALPHA.has(c)) {
+ length++;
+ c = reader.peek(length);
+ }
+ // If the name would be empty, an error occurs.
+ if (length == 0) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected alphabetic or numeric character, but found " + s + "(" + c + ")",
+ reader.getMark());
+ }
+ String value = reader.prefixForward(length);
+ c = reader.peek();
+ if (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected alphabetic or numeric character, but found " + s + "(" + c + ")",
+ reader.getMark());
+ }
+ return value;
+ }
+
+ private List<Integer> scanYamlDirectiveValue(Mark startMark) {
+ // See the specification for details.
+ while (reader.peek() == ' ') {
+ reader.forward();
+ }
+ Integer major = scanYamlDirectiveNumber(startMark);
+ int c = reader.peek();
+ if (c != '.') {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected a digit or '.', but found " + s + "(" + c + ")", reader.getMark());
+ }
+ reader.forward();
+ Integer minor = scanYamlDirectiveNumber(startMark);
+ c = reader.peek();
+ if (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected a digit or ' ', but found " + s + "(" + c + ")", reader.getMark());
+ }
+ List<Integer> result = new ArrayList<Integer>(2);
+ result.add(major);
+ result.add(minor);
+ return result;
+ }
+
+ /**
+ * Read a %YAML directive number: this is either the major or the minor part. Stop reading at a
+ * non-digit character (usually either '.' or '\n').
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id895631">7.1.1. “YAML” Directive</a>
+ * @see <a href="http://www.yaml.org/spec/1.1/#ns-dec-digit"></a>
+ */
+ private Integer scanYamlDirectiveNumber(Mark startMark) {
+ // See the specification for details.
+ int c = reader.peek();
+ if (!Character.isDigit(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected a digit, but found " + s + "(" + (c) + ")", reader.getMark());
+ }
+ int length = 0;
+ while (Character.isDigit(reader.peek(length))) {
+ length++;
+ }
+ Integer value = Integer.parseInt(reader.prefixForward(length));
+ return value;
+ }
+
+ /**
+ * <p>
+ * Read a %TAG directive value:
+ *
+ * <pre>
+ * s-ignored-space+ c-tag-handle s-ignored-space+ ns-tag-prefix s-l-comments
+ * </pre>
+ *
+ * </p>
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id896044">7.1.2. “TAG” Directive</a>
+ */
+ private List<String> scanTagDirectiveValue(Mark startMark) {
+ // See the specification for details.
+ while (reader.peek() == ' ') {
+ reader.forward();
+ }
+ String handle = scanTagDirectiveHandle(startMark);
+ while (reader.peek() == ' ') {
+ reader.forward();
+ }
+ String prefix = scanTagDirectivePrefix(startMark);
+ List<String> result = new ArrayList<String>(2);
+ result.add(handle);
+ result.add(prefix);
+ return result;
+ }
+
+ /**
+ * Scan a %TAG directive's handle. This is YAML's c-tag-handle.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id896876">7.1.2.2. Tag Handles</a>
+ * @param startMark - beginning of the handle
+ * @return scanned handle
+ */
+ private String scanTagDirectiveHandle(Mark startMark) {
+ // See the specification for details.
+ String value = scanTagHandle("directive", startMark);
+ int c = reader.peek();
+ if (c != ' ') {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected ' ', but found " + s + "(" + c + ")", reader.getMark());
+ }
+ return value;
+ }
+
+ /**
+ * Scan a %TAG directive's prefix. This is YAML's ns-tag-prefix.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#ns-tag-prefix"></a>
+ */
+ private String scanTagDirectivePrefix(Mark startMark) {
+ // See the specification for details.
+ String value = scanTagUri("directive", startMark);
+ int c = reader.peek();
+ if (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected ' ', but found " + s + "(" + c + ")", reader.getMark());
+ }
+ return value;
+ }
+
+ private CommentToken scanDirectiveIgnoredLine(Mark startMark) {
+ // See the specification for details.
+ while (reader.peek() == ' ') {
+ reader.forward();
+ }
+ CommentToken commentToken = null;
+ if (reader.peek() == '#') {
+ CommentToken comment = scanComment(CommentType.IN_LINE);
+ if (parseComments) {
+ commentToken = comment;
+ }
+ }
+ int c = reader.peek();
+ String lineBreak = scanLineBreak();
+ if (lineBreak.length() == 0 && c != '\0') {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a directive", startMark,
+ "expected a comment or a line break, but found " + s + "(" + c + ")", reader.getMark());
+ }
+ return commentToken;
+ }
+
+ /**
+ * <pre>
+ * The YAML 1.1 specification does not restrict characters for anchors and
+ * aliases. This may lead to problems.
+ * see https://bitbucket.org/snakeyaml/snakeyaml/issues/485/alias-names-are-too-permissive-compared-to
+ * This implementation tries to follow https://github.com/yaml/yaml-spec/blob/master/rfc/RFC-0003.md
+ * </pre>
+ */
+ private Token scanAnchor(boolean isAnchor) {
+ Mark startMark = reader.getMark();
+ int indicator = reader.peek();
+ String name = indicator == '*' ? "alias" : "anchor";
+ reader.forward();
+ int length = 0;
+ int c = reader.peek(length);
+ while (Constant.NULL_BL_T_LINEBR.hasNo(c, ":,[]{}/.*&")) {
+ length++;
+ c = reader.peek(length);
+ }
+ if (length == 0) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning an " + name, startMark,
+ "unexpected character found " + s + "(" + c + ")", reader.getMark());
+ }
+ String value = reader.prefixForward(length);
+ c = reader.peek();
+ if (Constant.NULL_BL_T_LINEBR.hasNo(c, "?:,]}%@`")) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning an " + name, startMark,
+ "unexpected character found " + s + "(" + c + ")", reader.getMark());
+ }
+ Mark endMark = reader.getMark();
+ Token tok;
+ if (isAnchor) {
+ tok = new AnchorToken(value, startMark, endMark);
+ } else {
+ tok = new AliasToken(value, startMark, endMark);
+ }
+ return tok;
+ }
+
+ /**
+ * <p>
+ * Scan a Tag property. A Tag property may be specified in one of three ways: c-verbatim-tag,
+ * c-ns-shorthand-tag, or c-ns-non-specific-tag
+ * </p>
+ *
+ * <p>
+ * c-verbatim-tag takes the form !&lt;ns-uri-char+&gt; and must be delivered verbatim (as-is) to
+ * the application. In particular, verbatim tags are not subject to tag resolution.
+ * </p>
+ *
+ * <p>
+ * c-ns-shorthand-tag is a valid tag handle followed by a non-empty suffix. If the tag handle is a
+ * c-primary-tag-handle ('!') then the suffix must have all exclamation marks properly URI-escaped
+ * (%21); otherwise, the string will look like a named tag handle: !foo!bar would be interpreted
+ * as (handle="!foo!", suffix="bar").
+ * </p>
+ *
+ * <p>
+ * c-ns-non-specific-tag is always a lone '!'; this is only useful for plain scalars, where its
+ * specification means that the scalar MUST be resolved to have type tag:yaml.org,2002:str.
+ * </p>
+ *
+ * TODO SnakeYaml incorrectly ignores c-ns-non-specific-tag right now.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id900262">8.2. Node Tags</a>
+ *
+ * TODO Note that this method does not enforce rules about local versus global tags!
+ */
+ private Token scanTag() {
+ // See the specification for details.
+ Mark startMark = reader.getMark();
+ // Determine the type of tag property based on the first character
+ // encountered
+ int c = reader.peek(1);
+ String handle = null;
+ String suffix = null;
+ // Verbatim tag! (c-verbatim-tag)
+ if (c == '<') {
+ // Skip the exclamation mark and &gt;, then read the tag suffix (as
+ // a URI).
+ reader.forward(2);
+ suffix = scanTagUri("tag", startMark);
+ c = reader.peek();
+ if (c != '>') {
+ // If there are any characters between the end of the tag-suffix
+ // URI and the closing &gt;, then an error has occurred.
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a tag", startMark,
+ "expected '>', but found '" + s + "' (" + c + ")", reader.getMark());
+ }
+ reader.forward();
+ } else if (Constant.NULL_BL_T_LINEBR.has(c)) {
+ // A NUL, blank, tab, or line-break means that this was a
+ // c-ns-non-specific tag.
+ suffix = "!";
+ reader.forward();
+ } else {
+ // Any other character implies c-ns-shorthand-tag type.
+
+ // Look ahead in the stream to determine whether this tag property
+ // is of the form !foo or !foo!bar.
+ int length = 1;
+ boolean useHandle = false;
+ while (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ if (c == '!') {
+ useHandle = true;
+ break;
+ }
+ length++;
+ c = reader.peek(length);
+ }
+ // If we need to use a handle, scan it in; otherwise, the handle is
+ // presumed to be '!'.
+ if (useHandle) {
+ handle = scanTagHandle("tag", startMark);
+ } else {
+ handle = "!";
reader.forward();
- Mark endMark = reader.getMark();
- Token token;
- if (isMappingEnd) {
- token = new FlowMappingEndToken(startMark, endMark);
+ }
+ suffix = scanTagUri("tag", startMark);
+ }
+ c = reader.peek();
+ // Check that the next character is allowed to follow a tag-property;
+ // if it is not, raise the error.
+ if (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a tag", startMark,
+ "expected ' ', but found '" + s + "' (" + (c) + ")", reader.getMark());
+ }
+ TagTuple value = new TagTuple(handle, suffix);
+ Mark endMark = reader.getMark();
+ return new TagToken(value, startMark, endMark);
+ }
+
+ private List<Token> scanBlockScalar(char style) {
+ // See the specification for details.
+ boolean folded;
+ // Depending on the given style, we determine whether the scalar is
+ // folded ('>') or literal ('|')
+ folded = style == '>';
+ StringBuilder chunks = new StringBuilder();
+ Mark startMark = reader.getMark();
+ // Scan the header.
+ reader.forward();
+ Chomping chompi = scanBlockScalarIndicators(startMark);
+ int increment = chompi.getIncrement();
+ CommentToken commentToken = scanBlockScalarIgnoredLine(startMark);
+
+ // Determine the indentation level and go to the first non-empty line.
+ int minIndent = this.indent + 1;
+ if (minIndent < 1) {
+ minIndent = 1;
+ }
+ String breaks;
+ int maxIndent;
+ int indent;
+ Mark endMark;
+ if (increment == -1) {
+ Object[] brme = scanBlockScalarIndentation();
+ breaks = (String) brme[0];
+ maxIndent = ((Integer) brme[1]).intValue();
+ endMark = (Mark) brme[2];
+ indent = Math.max(minIndent, maxIndent);
+ } else {
+ indent = minIndent + increment - 1;
+ Object[] brme = scanBlockScalarBreaks(indent);
+ breaks = (String) brme[0];
+ endMark = (Mark) brme[1];
+ }
+
+ String lineBreak = "";
+
+ // Scan the inner part of the block scalar.
+ while (this.reader.getColumn() == indent && reader.peek() != '\0') {
+ chunks.append(breaks);
+ boolean leadingNonSpace = " \t".indexOf(reader.peek()) == -1;
+ int length = 0;
+ while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(length))) {
+ length++;
+ }
+ chunks.append(reader.prefixForward(length));
+ lineBreak = scanLineBreak();
+ Object[] brme = scanBlockScalarBreaks(indent);
+ breaks = (String) brme[0];
+ endMark = (Mark) brme[1];
+ if (this.reader.getColumn() == indent && reader.peek() != '\0') {
+
+ // Unfortunately, folding rules are ambiguous.
+ //
+ // This is the folding according to the specification:
+ if (folded && "\n".equals(lineBreak) && leadingNonSpace
+ && " \t".indexOf(reader.peek()) == -1) {
+ if (breaks.length() == 0) {
+ chunks.append(" ");
+ }
} else {
- token = new FlowSequenceEndToken(startMark, endMark);
+ chunks.append(lineBreak);
+ }
+ // Clark Evans's interpretation (also in the spec examples) not
+ // imported from PyYAML
+ } else {
+ break;
+ }
+ }
+ // Chomp the tail.
+ if (chompi.chompTailIsNotFalse()) {
+ chunks.append(lineBreak);
+ }
+ if (chompi.chompTailIsTrue()) {
+ chunks.append(breaks);
+ }
+ // We are done.
+ ScalarToken scalarToken = new ScalarToken(chunks.toString(), false, startMark, endMark,
+ DumperOptions.ScalarStyle.createStyle(style));
+ return makeTokenList(commentToken, scalarToken);
+ }
+
+ /**
+ * Scan a block scalar indicator. The block scalar indicator includes two optional components,
+ * which may appear in either order.
+ *
+ * A block indentation indicator is a non-zero digit describing the indentation level of the block
+ * scalar to follow. This indentation is an additional number of spaces relative to the current
+ * indentation level.
+ *
+ * A block chomping indicator is a + or -, selecting the chomping mode away from the default
+ * (clip) to either -(strip) or +(keep).
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id868988">5.3. Indicator Characters</a>
+ * @see <a href="http://www.yaml.org/spec/1.1/#id927035">9.2.2. Block Indentation Indicator</a>
+ * @see <a href="http://www.yaml.org/spec/1.1/#id927557">9.2.3. Block Chomping Indicator</a>
+ */
+ private Chomping scanBlockScalarIndicators(Mark startMark) {
+ // See the specification for details.
+ Boolean chomping = null;
+ int increment = -1;
+ int c = reader.peek();
+ if (c == '-' || c == '+') {
+ if (c == '+') {
+ chomping = Boolean.TRUE;
+ } else {
+ chomping = Boolean.FALSE;
+ }
+ reader.forward();
+ c = reader.peek();
+ if (Character.isDigit(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ increment = Integer.parseInt(s);
+ if (increment == 0) {
+ throw new ScannerException("while scanning a block scalar", startMark,
+ "expected indentation indicator in the range 1-9, but found 0", reader.getMark());
}
- this.tokens.add(token);
- }
-
- /**
- * Fetch an entry in the flow style. Flow-style entries occur either
- * immediately after the start of a collection, or else after a comma.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchFlowEntry() {
- // Simple keys are allowed after ','.
- this.allowSimpleKey = true;
-
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
-
- // Add FLOW-ENTRY.
- Mark startMark = reader.getMark();
reader.forward();
- Mark endMark = reader.getMark();
- Token token = new FlowEntryToken(startMark, endMark);
- this.tokens.add(token);
- }
-
- /**
- * Fetch an entry in the block style.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchBlockEntry() {
- // Block context needs additional checks.
- if (this.flowLevel == 0) {
- // Are we allowed to start a new entry?
- if (!this.allowSimpleKey) {
- throw new ScannerException(null, null, "sequence entries are not allowed here",
- reader.getMark());
- }
-
- // We may need to add BLOCK-SEQUENCE-START.
- if (addIndent(this.reader.getColumn())) {
- Mark mark = reader.getMark();
- this.tokens.add(new BlockSequenceStartToken(mark, mark));
- }
+ }
+ } else if (Character.isDigit(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ increment = Integer.parseInt(s);
+ if (increment == 0) {
+ throw new ScannerException("while scanning a block scalar", startMark,
+ "expected indentation indicator in the range 1-9, but found 0", reader.getMark());
+ }
+ reader.forward();
+ c = reader.peek();
+ if (c == '-' || c == '+') {
+ if (c == '+') {
+ chomping = Boolean.TRUE;
} else {
- // It's an error for the block entry to occur in the flow
- // context,but we let the parser detect this.
+ chomping = Boolean.FALSE;
}
- // Simple keys are allowed after '-'.
- this.allowSimpleKey = true;
-
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
-
- // Add BLOCK-ENTRY.
- Mark startMark = reader.getMark();
reader.forward();
- Mark endMark = reader.getMark();
- Token token = new BlockEntryToken(startMark, endMark);
- this.tokens.add(token);
- }
-
- /**
- * Fetch a key in a block-style mapping.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchKey() {
- // Block context needs additional checks.
- if (this.flowLevel == 0) {
- // Are we allowed to start a key (not necessary a simple)?
- if (!this.allowSimpleKey) {
- throw new ScannerException(null, null, "mapping keys are not allowed here",
- reader.getMark());
- }
- // We may need to add BLOCK-MAPPING-START.
- if (addIndent(this.reader.getColumn())) {
- Mark mark = reader.getMark();
- this.tokens.add(new BlockMappingStartToken(mark, mark));
- }
- }
- // Simple keys are allowed after '?' in the block context.
- this.allowSimpleKey = this.flowLevel == 0;
-
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
-
- // Add KEY.
- Mark startMark = reader.getMark();
+ }
+ }
+ c = reader.peek();
+ if (Constant.NULL_BL_LINEBR.hasNo(c)) {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a block scalar", startMark,
+ "expected chomping or indentation indicators, but found " + s + "(" + c + ")",
+ reader.getMark());
+ }
+ return new Chomping(chomping, increment);
+ }
+
+ /**
+ * Scan to the end of the line after a block scalar has been scanned; the only things that are
+ * permitted at this time are comments and spaces.
+ */
+ private CommentToken scanBlockScalarIgnoredLine(Mark startMark) {
+ // See the specification for details.
+
+ // Forward past any number of trailing spaces
+ while (reader.peek() == ' ') {
+ reader.forward();
+ }
+
+ // If a comment occurs, scan to just before the end of line.
+ CommentToken commentToken = null;
+ if (reader.peek() == '#') {
+ commentToken = scanComment(CommentType.IN_LINE);
+ }
+ // If the next character is not a null or line break, an error has
+ // occurred.
+ int c = reader.peek();
+ String lineBreak = scanLineBreak();
+ if (lineBreak.length() == 0 && c != '\0') {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a block scalar", startMark,
+ "expected a comment or a line break, but found " + s + "(" + c + ")", reader.getMark());
+ }
+ return commentToken;
+ }
+
+ /**
+ * Scans for the indentation of a block scalar implicitly. This mechanism is used only if the
+ * block did not explicitly state an indentation to be used.
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#id927035">9.2.2. Block Indentation Indicator</a>
+ */
+ private Object[] scanBlockScalarIndentation() {
+ // See the specification for details.
+ StringBuilder chunks = new StringBuilder();
+ int maxIndent = 0;
+ Mark endMark = reader.getMark();
+ // Look ahead some number of lines until the first non-blank character
+ // occurs; the determined indentation will be the maximum number of
+ // leading spaces on any of these lines.
+ while (Constant.LINEBR.has(reader.peek(), " \r")) {
+ if (reader.peek() != ' ') {
+ // If the character isn't a space, it must be some kind of
+ // line-break; scan the line break and track it.
+ chunks.append(scanLineBreak());
+ endMark = reader.getMark();
+ } else {
+ // If the character is a space, move forward to the next
+ // character; if we surpass our previous maximum for indent
+ // level, update that too.
reader.forward();
- Mark endMark = reader.getMark();
- Token token = new KeyToken(startMark, endMark);
- this.tokens.add(token);
- }
-
- /**
- * Fetch a value in a block-style mapping.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchValue() {
- // Do we determine a simple key?
- SimpleKey key = this.possibleSimpleKeys.remove(this.flowLevel);
- if (key != null) {
- // Add KEY.
- this.tokens.add(key.getTokenNumber() - this.tokensTaken, new KeyToken(key.getMark(),
- key.getMark()));
-
- // If this key starts a new block mapping, we need to add
- // BLOCK-MAPPING-START.
- if (this.flowLevel == 0) {
- if (addIndent(key.getColumn())) {
- this.tokens.add(key.getTokenNumber() - this.tokensTaken,
- new BlockMappingStartToken(key.getMark(), key.getMark()));
- }
- }
- // There cannot be two simple keys one after another.
- this.allowSimpleKey = false;
-
- } else {
- // It must be a part of a complex key.
- // Block context needs additional checks. Do we really need them?
- // They will be caught by the parser anyway.
- if (this.flowLevel == 0) {
-
- // We are allowed to start a complex value if and only if we can
- // start a simple key.
- if (!this.allowSimpleKey) {
- throw new ScannerException(null, null, "mapping values are not allowed here",
- reader.getMark());
- }
- }
-
- // If this value starts a new block mapping, we need to add
- // BLOCK-MAPPING-START. It will be detected as an error later by
- // the parser.
- if (flowLevel == 0) {
- if (addIndent(reader.getColumn())) {
- Mark mark = reader.getMark();
- this.tokens.add(new BlockMappingStartToken(mark, mark));
- }
- }
-
- // Simple keys are allowed after ':' in the block context.
- allowSimpleKey = flowLevel == 0;
-
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
- }
- // Add VALUE.
- Mark startMark = reader.getMark();
- reader.forward();
- Mark endMark = reader.getMark();
- Token token = new ValueToken(startMark, endMark);
- this.tokens.add(token);
- }
-
- /**
- * Fetch an alias, which is a reference to an anchor. Aliases take the
- * format:
- *
- * <pre>
- * *(anchor name)
- * </pre>
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863390"></a>
- */
- private void fetchAlias() {
- // ALIAS could be a simple key.
- savePossibleSimpleKey();
-
- // No simple keys after ALIAS.
- this.allowSimpleKey = false;
-
- // Scan and add ALIAS.
- Token tok = scanAnchor(false);
- this.tokens.add(tok);
- }
-
- /**
- * Fetch an anchor. Anchors take the form:
- *
- * <pre>
- * &(anchor name)
- * </pre>
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863390"></a>
- */
- private void fetchAnchor() {
- // ANCHOR could start a simple key.
- savePossibleSimpleKey();
-
- // No simple keys after ANCHOR.
- this.allowSimpleKey = false;
-
- // Scan and add ANCHOR.
- Token tok = scanAnchor(true);
- this.tokens.add(tok);
- }
-
- /**
- * Fetch a tag. Tags take a complex form.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id861700"></a>
- */
- private void fetchTag() {
- // TAG could start a simple key.
- savePossibleSimpleKey();
-
- // No simple keys after TAG.
- this.allowSimpleKey = false;
-
- // Scan and add TAG.
- Token tok = scanTag();
- this.tokens.add(tok);
- }
-
- /**
- * Fetch a literal scalar, denoted with a vertical-bar. This is the type
- * best used for source code and other content, such as binary data, which
- * must be included verbatim.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchLiteral() {
- fetchBlockScalar('|');
- }
-
- /**
- * Fetch a folded scalar, denoted with a greater-than sign. This is the type
- * best used for long content, such as the text of a chapter or description.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- */
- private void fetchFolded() {
- fetchBlockScalar('>');
- }
-
- /**
- * Fetch a block scalar (literal or folded).
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- *
- * @param style
- */
- private void fetchBlockScalar(char style) {
- // A simple key may follow a block scalar.
- this.allowSimpleKey = true;
-
- // Reset possible simple key on the current level.
- removePossibleSimpleKey();
-
- // Scan and add SCALAR.
- Token tok = scanBlockScalar(style);
- this.tokens.add(tok);
- }
-
- /**
- * Fetch a single-quoted (') scalar.
- */
- private void fetchSingle() {
- fetchFlowScalar('\'');
- }
-
- /**
- * Fetch a double-quoted (") scalar.
- */
- private void fetchDouble() {
- fetchFlowScalar('"');
- }
-
- /**
- * Fetch a flow scalar (single- or double-quoted).
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id863975"></a>
- *
- * @param style
- */
- private void fetchFlowScalar(char style) {
- // A flow scalar could be a simple key.
- savePossibleSimpleKey();
-
- // No simple keys after flow scalars.
- this.allowSimpleKey = false;
-
- // Scan and add SCALAR.
- Token tok = scanFlowScalar(style);
- this.tokens.add(tok);
- }
-
- /**
- * Fetch a plain scalar.
- */
- private void fetchPlain() {
- // A plain scalar could be a simple key.
- savePossibleSimpleKey();
-
- // No simple keys after plain scalars. But note that `scan_plain` will
- // change this flag if the scan is finished at the beginning of the
- // line.
- this.allowSimpleKey = false;
-
- // Scan and add SCALAR. May change `allow_simple_key`.
- Token tok = scanPlain();
- this.tokens.add(tok);
- }
-
- // Checkers.
- /**
- * Returns true if the next thing on the reader is a directive, given that
- * the leading '%' has already been checked.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id864824"></a>
- */
- private boolean checkDirective() {
- // DIRECTIVE: ^ '%' ...
- // The '%' indicator is already checked.
- return reader.getColumn() == 0;
- }
-
- /**
- * Returns true if the next thing on the reader is a document-start ("---").
- * A document-start is always followed immediately by a new line.
- */
- private boolean checkDocumentStart() {
- // DOCUMENT-START: ^ '---' (' '|'\n')
- if (reader.getColumn() == 0) {
- if ("---".equals(reader.prefix(3)) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the next thing on the reader is a document-end ("..."). A
- * document-end is always followed immediately by a new line.
- */
- private boolean checkDocumentEnd() {
- // DOCUMENT-END: ^ '...' (' '|'\n')
- if (reader.getColumn() == 0) {
- if ("...".equals(reader.prefix(3)) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the next thing on the reader is a block token.
- */
- private boolean checkBlockEntry() {
- // BLOCK-ENTRY: '-' (' '|'\n')
- return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
- }
-
- /**
- * Returns true if the next thing on the reader is a key token.
- */
- private boolean checkKey() {
- // KEY(flow context): '?'
- if (this.flowLevel != 0) {
- return true;
- } else {
- // KEY(block context): '?' (' '|'\n')
- return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
- }
- }
-
- /**
- * Returns true if the next thing on the reader is a value token.
- */
- private boolean checkValue() {
- // VALUE(flow context): ':'
- if (flowLevel != 0) {
- return true;
- } else {
- // VALUE(block context): ':' (' '|'\n')
- return Constant.NULL_BL_T_LINEBR.has(reader.peek(1));
- }
- }
-
- /**
- * Returns true if the next thing on the reader is a plain token.
- */
- private boolean checkPlain() {
- /**
- * <pre>
- * A plain scalar may start with any non-space character except:
- * '-', '?', ':', ',', '[', ']', '{', '}',
- * '#', '&amp;', '*', '!', '|', '&gt;', '\'', '\&quot;',
- * '%', '@', '`'.
- *
- * It may also start with
- * '-', '?', ':'
- * if it is followed by a non-space character.
- *
- * Note that we limit the last rule to the block context (except the
- * '-' character) because we want the flow context to be space
- * independent.
- * </pre>
- */
- char ch = reader.peek();
- // If the next char is NOT one of the forbidden chars above or
- // whitespace, then this is the start of a plain scalar.
- return Constant.NULL_BL_T_LINEBR.hasNo(ch, "-?:,[]{}#&*!|>\'\"%@`")
- || (Constant.NULL_BL_T_LINEBR.hasNo(reader.peek(1)) && (ch == '-' || (this.flowLevel == 0 && "?:"
- .indexOf(ch) != -1)));
- }
-
- // Scanners.
-
- /**
- * <pre>
- * We ignore spaces, line breaks and comments.
- * If we find a line break in the block context, we set the flag
- * `allow_simple_key` on.
- * The byte order mark is stripped if it's the first character in the
- * stream. We do not yet support BOM inside the stream as the
- * specification requires. Any such mark will be considered as a part
- * of the document.
- * TODO: We need to make tab handling rules more sane. A good rule is
- * Tabs cannot precede tokens
- * BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
- * KEY(block), VALUE(block), BLOCK-ENTRY
- * So the checking code is
- * if &lt;TAB&gt;:
- * self.allow_simple_keys = False
- * We also need to add the check for `allow_simple_keys == True` to
- * `unwind_indent` before issuing BLOCK-END.
- * Scanners for block, flow, and plain scalars need to be modified.
- * </pre>
- */
- private void scanToNextToken() {
- // If there is a byte order mark (BOM) at the beginning of the stream,
- // forward past it.
- if (reader.getIndex() == 0 && reader.peek() == '\uFEFF') {
- reader.forward();
- }
- boolean found = false;
- while (!found) {
- int ff = 0;
- // Peek ahead until we find the first non-space character, then
- // move forward directly to that character.
- while (reader.peek(ff) == ' ') {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- // If the character we have skipped forward to is a comment (#),
- // then peek ahead until we find the next end of line. YAML
- // comments are from a # to the next new-line. We then forward
- // past the comment.
- if (reader.peek() == '#') {
- ff = 0;
- while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(ff))) {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- }
- // If we scanned a line break, then (depending on flow level),
- // simple keys may be allowed.
- if (scanLineBreak().length() != 0) {// found a line-break
- if (this.flowLevel == 0) {
- // Simple keys are allowed at flow-level 0 after a line
- // break
- this.allowSimpleKey = true;
- }
- } else {
- found = true;
- }
- }
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- private Token scanDirective() {
- // See the specification for details.
- Mark startMark = reader.getMark();
- Mark endMark;
+ if (this.reader.getColumn() > maxIndent) {
+ maxIndent = reader.getColumn();
+ }
+ }
+ }
+ // Pass several results back together.
+ return new Object[] {chunks.toString(), maxIndent, endMark};
+ }
+
+ private Object[] scanBlockScalarBreaks(int indent) {
+ // See the specification for details.
+ StringBuilder chunks = new StringBuilder();
+ Mark endMark = reader.getMark();
+ int col = this.reader.getColumn();
+ // Scan for up to the expected indentation-level of spaces, then move
+ // forward past that amount.
+ while (col < indent && reader.peek() == ' ') {
+ reader.forward();
+ col++;
+ }
+
+ // Consume one or more line breaks followed by any amount of spaces,
+ // until we find something that isn't a line-break.
+ String lineBreak = null;
+ while ((lineBreak = scanLineBreak()).length() != 0) {
+ chunks.append(lineBreak);
+ endMark = reader.getMark();
+ // Scan past up to (indent) spaces on the next line, then forward
+ // past them.
+ col = this.reader.getColumn();
+ while (col < indent && reader.peek() == ' ') {
reader.forward();
- String name = scanDirectiveName(startMark);
- List<?> value = null;
- if ("YAML".equals(name)) {
- value = scanYamlDirectiveValue(startMark);
- endMark = reader.getMark();
- } else if ("TAG".equals(name)) {
- value = scanTagDirectiveValue(startMark);
- endMark = reader.getMark();
- } else {
- endMark = reader.getMark();
- int ff = 0;
- while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(ff))) {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- }
- scanDirectiveIgnoredLine(startMark);
- return new DirectiveToken(name, value, startMark, endMark);
- }
-
- /**
- * Scan a directive name. Directive names are a series of non-space
- * characters.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id895217"></a>
- */
- private String scanDirectiveName(Mark startMark) {
- // See the specification for details.
- int length = 0;
- // A Directive-name is a sequence of alphanumeric characters
- // (a-z,A-Z,0-9). We scan until we find something that isn't.
- // FIXME this disagrees with the specification.
- char ch = reader.peek(length);
- while (Constant.ALPHA.has(ch)) {
- length++;
- ch = reader.peek(length);
- }
- // If the name would be empty, an error occurs.
- if (length == 0) {
- throw new ScannerException("while scanning a directive", startMark,
- "expected alphabetic or numeric character, but found " + ch + "(" + ((int) ch)
- + ")", reader.getMark());
- }
- String value = reader.prefixForward(length);
- ch = reader.peek();
- if (Constant.NULL_BL_LINEBR.hasNo(ch)) {
- throw new ScannerException("while scanning a directive", startMark,
- "expected alphabetic or numeric character, but found " + ch + "(" + ((int) ch)
- + ")", reader.getMark());
- }
- return value;
- }
-
- private List<Integer> scanYamlDirectiveValue(Mark startMark) {
- // See the specification for details.
- while (reader.peek() == ' ') {
- reader.forward();
- }
- Integer major = scanYamlDirectiveNumber(startMark);
- if (reader.peek() != '.') {
- throw new ScannerException("while scanning a directive", startMark,
- "expected a digit or '.', but found " + reader.peek() + "("
- + ((int) reader.peek()) + ")", reader.getMark());
- }
+ col++;
+ }
+ }
+ // Return both the assembled intervening string and the end-mark.
+ return new Object[] {chunks.toString(), endMark};
+ }
+
+ /**
+ * Scan a flow-style scalar. Flow scalars are presented in one of two forms; first, a flow scalar
+ * may be a double-quoted string; second, a flow scalar may be a single-quoted string.
+ *
+ * @see <a href="https://yaml.org/spec/1.1/#id904158">9.1. Flow Scalar Styles</a> style/syntax
+ *
+ * <pre>
+ * See the specification for details.
+ * Note that we loose indentation rules for quoted scalars. Quoted
+ * scalars don't need to adhere indentation because &quot; and ' clearly
+ * mark the beginning and the end of them. Therefore we are less
+ * restrictive then the specification requires. We only need to check
+ * that document separators are not included in scalars.
+ * </pre>
+ */
+ private Token scanFlowScalar(char style) {
+ boolean _double;
+ // The style will be either single- or double-quoted; we determine this
+ // by the first character in the entry (supplied)
+ _double = style == '"';
+ StringBuilder chunks = new StringBuilder();
+ Mark startMark = reader.getMark();
+ int quote = reader.peek();
+ reader.forward();
+ chunks.append(scanFlowScalarNonSpaces(_double, startMark));
+ while (reader.peek() != quote) {
+ chunks.append(scanFlowScalarSpaces(startMark));
+ chunks.append(scanFlowScalarNonSpaces(_double, startMark));
+ }
+ reader.forward();
+ Mark endMark = reader.getMark();
+ return new ScalarToken(chunks.toString(), false, startMark, endMark,
+ DumperOptions.ScalarStyle.createStyle(style));
+ }
+
+ /**
+ * Scan some number of flow-scalar non-space characters.
+ */
+ private String scanFlowScalarNonSpaces(boolean doubleQuoted, Mark startMark) {
+ // See the specification for details.
+ StringBuilder chunks = new StringBuilder();
+ while (true) {
+ // Scan through any number of characters which are not: NUL, blank,
+ // tabs, line breaks, single-quotes, double-quotes, or backslashes.
+ int length = 0;
+ while (Constant.NULL_BL_T_LINEBR.hasNo(reader.peek(length), "'\"\\")) {
+ length++;
+ }
+ if (length != 0) {
+ chunks.append(reader.prefixForward(length));
+ }
+ // Depending on our quoting-type, the characters ', " and \ have
+ // differing meanings.
+ int c = reader.peek();
+ if (!doubleQuoted && c == '\'' && reader.peek(1) == '\'') {
+ chunks.append("'");
+ reader.forward(2);
+ } else if ((doubleQuoted && c == '\'') || (!doubleQuoted && "\"\\".indexOf(c) != -1)) {
+ chunks.appendCodePoint(c);
reader.forward();
- Integer minor = scanYamlDirectiveNumber(startMark);
- if (Constant.NULL_BL_LINEBR.hasNo(reader.peek())) {
- throw new ScannerException("while scanning a directive", startMark,
- "expected a digit or ' ', but found " + reader.peek() + "("
- + ((int) reader.peek()) + ")", reader.getMark());
- }
- List<Integer> result = new ArrayList<Integer>(2);
- result.add(major);
- result.add(minor);
- return result;
- }
-
- /**
- * Read a %YAML directive number: this is either the major or the minor
- * part. Stop reading at a non-digit character (usually either '.' or '\n').
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id895631"></a>
- * @see <a href="http://www.yaml.org/spec/1.1/#ns-dec-digit"></a>
- */
- private Integer scanYamlDirectiveNumber(Mark startMark) {
- // See the specification for details.
- char ch = reader.peek();
- if (!Character.isDigit(ch)) {
- throw new ScannerException("while scanning a directive", startMark,
- "expected a digit, but found " + ch + "(" + ((int) ch) + ")", reader.getMark());
- }
- int length = 0;
- while (Character.isDigit(reader.peek(length))) {
- length++;
- }
- Integer value = Integer.parseInt(reader.prefixForward(length));
- return value;
- }
-
- /**
- * <p>
- * Read a %TAG directive value:
- *
- * <pre>
- * s-ignored-space+ c-tag-handle s-ignored-space+ ns-tag-prefix s-l-comments
- * </pre>
- *
- * </p>
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id896044"></a>
- */
- private List<String> scanTagDirectiveValue(Mark startMark) {
- // See the specification for details.
- while (reader.peek() == ' ') {
- reader.forward();
- }
- String handle = scanTagDirectiveHandle(startMark);
- while (reader.peek() == ' ') {
- reader.forward();
- }
- String prefix = scanTagDirectivePrefix(startMark);
- List<String> result = new ArrayList<String>(2);
- result.add(handle);
- result.add(prefix);
- return result;
- }
-
- /**
- * Scan a %TAG directive's handle. This is YAML's c-tag-handle.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id896876"></a>
- * @param startMark
- * @return
- */
- private String scanTagDirectiveHandle(Mark startMark) {
- // See the specification for details.
- String value = scanTagHandle("directive", startMark);
- char ch = reader.peek();
- if (ch != ' ') {
- throw new ScannerException("while scanning a directive", startMark,
- "expected ' ', but found " + reader.peek() + "(" + ch + ")", reader.getMark());
- }
- return value;
- }
-
- /**
- * Scan a %TAG directive's prefix. This is YAML's ns-tag-prefix.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#ns-tag-prefix"></a>
- */
- private String scanTagDirectivePrefix(Mark startMark) {
- // See the specification for details.
- String value = scanTagUri("directive", startMark);
- if (Constant.NULL_BL_LINEBR.hasNo(reader.peek())) {
- throw new ScannerException("while scanning a directive", startMark,
- "expected ' ', but found " + reader.peek() + "(" + ((int) reader.peek()) + ")",
- reader.getMark());
- }
- return value;
- }
-
- private String scanDirectiveIgnoredLine(Mark startMark) {
- // See the specification for details.
- int ff = 0;
- while (reader.peek(ff) == ' ') {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- if (reader.peek() == '#') {
- ff = 0;
- while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(ff))) {
- ff++;
- }
- reader.forward(ff);
- }
- char ch = reader.peek();
- String lineBreak = scanLineBreak();
- if (lineBreak.length() == 0 && ch != '\0') {
- throw new ScannerException("while scanning a directive", startMark,
- "expected a comment or a line break, but found " + ch + "(" + ((int) ch) + ")",
- reader.getMark());
- }
- return lineBreak;
- }
-
- /**
- * <pre>
- * The specification does not restrict characters for anchors and
- * aliases. This may lead to problems, for instance, the document:
- * [ *alias, value ]
- * can be interpreted in two ways, as
- * [ &quot;value&quot; ]
- * and
- * [ *alias , &quot;value&quot; ]
- * Therefore we restrict aliases to numbers and ASCII letters.
- * </pre>
- */
- private Token scanAnchor(boolean isAnchor) {
- Mark startMark = reader.getMark();
- char indicator = reader.peek();
- String name = indicator == '*' ? "alias" : "anchor";
+ } else if (doubleQuoted && c == '\\') {
reader.forward();
- int length = 0;
- char ch = reader.peek(length);
- while (Constant.ALPHA.has(ch)) {
- length++;
- ch = reader.peek(length);
- }
- if (length == 0) {
- throw new ScannerException("while scanning an " + name, startMark,
- "expected alphabetic or numeric character, but found " + ch,
- reader.getMark());
- }
- String value = reader.prefixForward(length);
- ch = reader.peek();
- if (Constant.NULL_BL_T_LINEBR.hasNo(ch, "?:,]}%@`")) {
- throw new ScannerException("while scanning an " + name, startMark,
- "expected alphabetic or numeric character, but found " + ch + "("
- + ((int) reader.peek()) + ")", reader.getMark());
- }
- Mark endMark = reader.getMark();
- Token tok;
- if (isAnchor) {
- tok = new AnchorToken(value, startMark, endMark);
- } else {
- tok = new AliasToken(value, startMark, endMark);
- }
- return tok;
- }
-
- /**
- * <p>
- * Scan a Tag property. A Tag property may be specified in one of three
- * ways: c-verbatim-tag, c-ns-shorthand-tag, or c-ns-non-specific-tag
- * </p>
- *
- * <p>
- * c-verbatim-tag takes the form !&lt;ns-uri-char+&gt; and must be delivered
- * verbatim (as-is) to the application. In particular, verbatim tags are not
- * subject to tag resolution.
- * </p>
- *
- * <p>
- * c-ns-shorthand-tag is a valid tag handle followed by a non-empty suffix.
- * If the tag handle is a c-primary-tag-handle ('!') then the suffix must
- * have all exclamation marks properly URI-escaped (%21); otherwise, the
- * string will look like a named tag handle: !foo!bar would be interpreted
- * as (handle="!foo!", suffix="bar").
- * </p>
- *
- * <p>
- * c-ns-non-specific-tag is always a lone '!'; this is only useful for plain
- * scalars, where its specification means that the scalar MUST be resolved
- * to have type tag:yaml.org,2002:str.
- * </p>
- *
- * TODO SnakeYaml incorrectly ignores c-ns-non-specific-tag right now.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id900262"></a>
- *
- * TODO Note that this method does not enforce rules about local versus
- * global tags!
- */
- private Token scanTag() {
- // See the specification for details.
- Mark startMark = reader.getMark();
- // Determine the type of tag property based on the first character
- // encountered
- char ch = reader.peek(1);
- String handle = null;
- String suffix = null;
- // Verbatim tag! (c-verbatim-tag)
- if (ch == '<') {
- // Skip the exclamation mark and &gt;, then read the tag suffix (as
- // a URI).
- reader.forward(2);
- suffix = scanTagUri("tag", startMark);
- if (reader.peek() != '>') {
- // If there are any characters between the end of the tag-suffix
- // URI and the closing &gt;, then an error has occurred.
- throw new ScannerException("while scanning a tag", startMark,
- "expected '>', but found '" + reader.peek() + "' (" + ((int) reader.peek())
- + ")", reader.getMark());
- }
- reader.forward();
- } else if (Constant.NULL_BL_T_LINEBR.has(ch)) {
- // A NUL, blank, tab, or line-break means that this was a
- // c-ns-non-specific tag.
- suffix = "!";
- reader.forward();
- } else {
- // Any other character implies c-ns-shorthand-tag type.
-
- // Look ahead in the stream to determine whether this tag property
- // is of the form !foo or !foo!bar.
- int length = 1;
- boolean useHandle = false;
- while (Constant.NULL_BL_LINEBR.hasNo(ch)) {
- if (ch == '!') {
- useHandle = true;
- break;
- }
- length++;
- ch = reader.peek(length);
- }
- handle = "!";
- // If we need to use a handle, scan it in; otherwise, the handle is
- // presumed to be '!'.
- if (useHandle) {
- handle = scanTagHandle("tag", startMark);
- } else {
- handle = "!";
- reader.forward();
- }
- suffix = scanTagUri("tag", startMark);
- }
- ch = reader.peek();
- // Check that the next character is allowed to follow a tag-property;
- // if it is not, raise the error.
- if (Constant.NULL_BL_LINEBR.hasNo(ch)) {
- throw new ScannerException("while scanning a tag", startMark,
- "expected ' ', but found '" + ch + "' (" + ((int) ch) + ")", reader.getMark());
- }
- TagTuple value = new TagTuple(handle, suffix);
- Mark endMark = reader.getMark();
- return new TagToken(value, startMark, endMark);
- }
-
- private Token scanBlockScalar(char style) {
- // See the specification for details.
- boolean folded;
- // Depending on the given style, we determine whether the scalar is
- // folded ('>') or literal ('|')
- if (style == '>') {
- folded = true;
+ c = reader.peek();
+ if (!Character.isSupplementaryCodePoint(c)
+ && ESCAPE_REPLACEMENTS.containsKey(Character.valueOf((char) c))) {
+ // The character is one of the single-replacement
+ // types; these are replaced with a literal character
+ // from the mapping.
+ chunks.append(ESCAPE_REPLACEMENTS.get(Character.valueOf((char) c)));
+ reader.forward();
+ } else if (!Character.isSupplementaryCodePoint(c)
+ && ESCAPE_CODES.containsKey(Character.valueOf((char) c))) {
+ // The character is a multi-digit escape sequence, with
+ // length defined by the value in the ESCAPE_CODES map.
+ length = ESCAPE_CODES.get(Character.valueOf((char) c)).intValue();
+ reader.forward();
+ String hex = reader.prefix(length);
+ if (NOT_HEXA.matcher(hex).find()) {
+ throw new ScannerException("while scanning a double-quoted scalar", startMark,
+ "expected escape sequence of " + length + " hexadecimal numbers, but found: " + hex,
+ reader.getMark());
+ }
+ int decimal = Integer.parseInt(hex, 16);
+ String unicode = new String(Character.toChars(decimal));
+ chunks.append(unicode);
+ reader.forward(length);
+ } else if (scanLineBreak().length() != 0) {
+ chunks.append(scanFlowScalarBreaks(startMark));
} else {
- folded = false;
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a double-quoted scalar", startMark,
+ "found unknown escape character " + s + "(" + c + ")", reader.getMark());
}
- StringBuilder chunks = new StringBuilder();
- Mark startMark = reader.getMark();
- // Scan the header.
+ } else {
+ return chunks.toString();
+ }
+ }
+ }
+
+ private String scanFlowScalarSpaces(Mark startMark) {
+ // See the specification for details.
+ StringBuilder chunks = new StringBuilder();
+ int length = 0;
+ // Scan through any number of whitespace (space, tab) characters,
+ // consuming them.
+ while (" \t".indexOf(reader.peek(length)) != -1) {
+ length++;
+ }
+ String whitespaces = reader.prefixForward(length);
+ int c = reader.peek();
+ if (c == '\0') {
+ // A flow scalar cannot end with an end-of-stream
+ throw new ScannerException("while scanning a quoted scalar", startMark,
+ "found unexpected end of stream", reader.getMark());
+ }
+ // If we encounter a line break, scan it into our assembled string...
+ String lineBreak = scanLineBreak();
+ if (lineBreak.length() != 0) {
+ String breaks = scanFlowScalarBreaks(startMark);
+ if (!"\n".equals(lineBreak)) {
+ chunks.append(lineBreak);
+ } else if (breaks.length() == 0) {
+ chunks.append(" ");
+ }
+ chunks.append(breaks);
+ } else {
+ chunks.append(whitespaces);
+ }
+ return chunks.toString();
+ }
+
+ private String scanFlowScalarBreaks(Mark startMark) {
+ // See the specification for details.
+ StringBuilder chunks = new StringBuilder();
+ while (true) {
+ // Instead of checking indentation, we check for document
+ // separators.
+ String prefix = reader.prefix(3);
+ if (("---".equals(prefix) || "...".equals(prefix))
+ && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
+ throw new ScannerException("while scanning a quoted scalar", startMark,
+ "found unexpected document separator", reader.getMark());
+ }
+ // Scan past any number of spaces and tabs, ignoring them
+ while (" \t".indexOf(reader.peek()) != -1) {
reader.forward();
- Chomping chompi = scanBlockScalarIndicators(startMark);
- int increment = chompi.getIncrement();
- scanBlockScalarIgnoredLine(startMark);
-
- // Determine the indentation level and go to the first non-empty line.
- int minIndent = this.indent + 1;
- if (minIndent < 1) {
- minIndent = 1;
- }
- String breaks = null;
- int maxIndent = 0;
- int indent = 0;
- Mark endMark;
- if (increment == -1) {
- Object[] brme = scanBlockScalarIndentation();
- breaks = (String) brme[0];
- maxIndent = ((Integer) brme[1]).intValue();
- endMark = (Mark) brme[2];
- indent = Math.max(minIndent, maxIndent);
+ }
+ // If we stopped at a line break, add that; otherwise, return the
+ // assembled set of scalar breaks.
+ String lineBreak = scanLineBreak();
+ if (lineBreak.length() != 0) {
+ chunks.append(lineBreak);
+ } else {
+ return chunks.toString();
+ }
+ }
+ }
+
+ /**
+ * Scan a plain scalar.
+ *
+ * <pre>
+ * See the specification for details.
+ * We add an additional restriction for the flow context:
+ * plain scalars in the flow context cannot contain ',', ':' and '?'.
+ * We also keep track of the `allow_simple_key` flag here.
+ * Indentation rules are loosed for the flow context.
+ * </pre>
+ */
+ private Token scanPlain() {
+ StringBuilder chunks = new StringBuilder();
+ Mark startMark = reader.getMark();
+ Mark endMark = startMark;
+ int indent = this.indent + 1;
+ String spaces = "";
+ while (true) {
+ int c;
+ int length = 0;
+ // A comment indicates the end of the scalar.
+ if (reader.peek() == '#') {
+ break;
+ }
+ while (true) {
+ c = reader.peek(length);
+ if (Constant.NULL_BL_T_LINEBR.has(c)
+ || (c == ':' && Constant.NULL_BL_T_LINEBR.has(reader.peek(length + 1),
+ flowLevel != 0 ? ",[]{}" : ""))
+ || (this.flowLevel != 0 && ",?[]{}".indexOf(c) != -1)) {
+ break;
+ }
+ length++;
+ }
+ if (length == 0) {
+ break;
+ }
+ this.allowSimpleKey = false;
+ chunks.append(spaces);
+ chunks.append(reader.prefixForward(length));
+ endMark = reader.getMark();
+ spaces = scanPlainSpaces();
+ // System.out.printf("spaces[%s]\n", spaces);
+ if (spaces.length() == 0 || reader.peek() == '#'
+ || (this.flowLevel == 0 && this.reader.getColumn() < indent)) {
+ break;
+ }
+ }
+ return new ScalarToken(chunks.toString(), startMark, endMark, true);
+ }
+
+ // Helper for scanPlainSpaces method when comments are enabled.
+ // The ensures that blank lines and comments following a multi-line plain token are not swallowed
+ // up
+ private boolean atEndOfPlain() {
+ // peak ahead to find end of whitespaces and the column at which it occurs
+ int wsLength = 0;
+ int wsColumn = this.reader.getColumn();
+ {
+ int c;
+ while ((c = reader.peek(wsLength)) != '\0' && Constant.NULL_BL_T_LINEBR.has(c)) {
+ wsLength++;
+ if (!Constant.LINEBR.has(c) && (c != '\r' || reader.peek(wsLength + 1) != '\n')
+ && c != 0xFEFF) {
+ wsColumn++;
} else {
- indent = minIndent + increment - 1;
- Object[] brme = scanBlockScalarBreaks(indent);
- breaks = (String) brme[0];
- endMark = (Mark) brme[1];
- }
-
- String lineBreak = "";
-
- // Scan the inner part of the block scalar.
- while (this.reader.getColumn() == indent && reader.peek() != '\0') {
- chunks.append(breaks);
- boolean leadingNonSpace = " \t".indexOf(reader.peek()) == -1;
- int length = 0;
- while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(length))) {
- length++;
- }
- chunks.append(reader.prefixForward(length));
- lineBreak = scanLineBreak();
- Object[] brme = scanBlockScalarBreaks(indent);
- breaks = (String) brme[0];
- endMark = (Mark) brme[1];
- if (this.reader.getColumn() == indent && reader.peek() != '\0') {
-
- // Unfortunately, folding rules are ambiguous.
- //
- // This is the folding according to the specification:
- if (folded && "\n".equals(lineBreak) && leadingNonSpace
- && " \t".indexOf(reader.peek()) == -1) {
- if (breaks.length() == 0) {
- chunks.append(" ");
- }
- } else {
- chunks.append(lineBreak);
- }
- // Clark Evans's interpretation (also in the spec examples) not
- // imported from PyYAML
- } else {
- break;
- }
- }
- // Chomp the tail.
- if (chompi.chompTailIsNotFalse()) {
- chunks.append(lineBreak);
+ wsColumn = 0;
}
- if (chompi.chompTailIsTrue()) {
- chunks.append(breaks);
- }
- // We are done.
- return new ScalarToken(chunks.toString(), false, startMark, endMark, style);
+ }
}
- /**
- * Scan a block scalar indicator. The block scalar indicator includes two
- * optional components, which may appear in either order.
- *
- * A block indentation indicator is a non-zero digit describing the
- * indentation level of the block scalar to follow. This indentation is an
- * additional number of spaces relative to the current indentation level.
- *
- * A block chomping indicator is a + or -, selecting the chomping mode away
- * from the default (clip) to either -(strip) or +(keep).
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id868988"></a>
- * @see <a href="http://www.yaml.org/spec/1.1/#id927035"></a>
- * @see <a href="http://www.yaml.org/spec/1.1/#id927557"></a>
- */
- private Chomping scanBlockScalarIndicators(Mark startMark) {
- // See the specification for details.
- Boolean chomping = null;
- int increment = -1;
- char ch = reader.peek();
- if (ch == '-' || ch == '+') {
- if (ch == '+') {
- chomping = Boolean.TRUE;
- } else {
- chomping = Boolean.FALSE;
- }
- reader.forward();
- ch = reader.peek();
- if (Character.isDigit(ch)) {
- increment = Integer.parseInt(String.valueOf(ch));
- if (increment == 0) {
- throw new ScannerException("while scanning a block scalar", startMark,
- "expected indentation indicator in the range 1-9, but found 0",
- reader.getMark());
- }
- reader.forward();
- }
- } else if (Character.isDigit(ch)) {
- increment = Integer.parseInt(String.valueOf(ch));
- if (increment == 0) {
- throw new ScannerException("while scanning a block scalar", startMark,
- "expected indentation indicator in the range 1-9, but found 0",
- reader.getMark());
- }
- reader.forward();
- ch = reader.peek();
- if (ch == '-' || ch == '+') {
- if (ch == '+') {
- chomping = Boolean.TRUE;
- } else {
- chomping = Boolean.FALSE;
- }
- reader.forward();
- }
- }
- ch = reader.peek();
- if (Constant.NULL_BL_LINEBR.hasNo(ch)) {
- throw new ScannerException("while scanning a block scalar", startMark,
- "expected chomping or indentation indicators, but found " + ch,
- reader.getMark());
- }
- return new Chomping(chomping, increment);
+ // if we see, a comment or end of string or change decrease in indent, we are done
+ // Do not chomp end of lines and blanks, they will be handled by the main loop.
+ if (reader.peek(wsLength) == '#' || reader.peek(wsLength + 1) == '\0'
+ || this.flowLevel == 0 && wsColumn < this.indent) {
+ return true;
}
- /**
- * Scan to the end of the line after a block scalar has been scanned; the
- * only things that are permitted at this time are comments and spaces.
- */
- private String scanBlockScalarIgnoredLine(Mark startMark) {
- // See the specification for details.
- int ff = 0;
- // Forward past any number of trailing spaces
- while (reader.peek(ff) == ' ') {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- // If a comment occurs, scan to just before the end of line.
- if (reader.peek() == '#') {
- ff = 0;
- while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(ff))) {
- ff++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- }
- // If the next character is not a null or line break, an error has
- // occurred.
- char ch = reader.peek();
- String lineBreak = scanLineBreak();
- if (lineBreak.length() == 0 && ch != '\0') {
- throw new ScannerException("while scanning a block scalar", startMark,
- "expected a comment or a line break, but found " + ch, reader.getMark());
+ // if we see, after the space, a key-value followed by a ':', we are done
+ // Do not chomp end of lines and blanks, they will be handled by the main loop.
+ if (this.flowLevel == 0) {
+ int c;
+ for (int extra = 1; (c = reader.peek(wsLength + extra)) != 0
+ && !Constant.NULL_BL_T_LINEBR.has(c); extra++) {
+ if (c == ':' && Constant.NULL_BL_T_LINEBR.has(reader.peek(wsLength + extra + 1))) {
+ return true;
}
- return lineBreak;
+ }
}
- /**
- * Scans for the indentation of a block scalar implicitly. This mechanism is
- * used only if the block did not explicitly state an indentation to be
- * used.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#id927035"></a>
- */
- private Object[] scanBlockScalarIndentation() {
- // See the specification for details.
- StringBuilder chunks = new StringBuilder();
- int maxIndent = 0;
- Mark endMark = reader.getMark();
- // Look ahead some number of lines until the first non-blank character
- // occurs; the determined indentation will be the maximum number of
- // leading spaces on any of these lines.
- while (Constant.LINEBR.has(reader.peek(), " \r")) {
- if (reader.peek() != ' ') {
- // If the character isn't a space, it must be some kind of
- // line-break; scan the line break and track it.
- chunks.append(scanLineBreak());
- endMark = reader.getMark();
- } else {
- // If the character is a space, move forward to the next
- // character; if we surpass our previous maximum for indent
- // level, update that too.
- reader.forward();
- if (this.reader.getColumn() > maxIndent) {
- maxIndent = reader.getColumn();
- }
- }
- }
- // Pass several results back together.
- return new Object[] { chunks.toString(), maxIndent, endMark };
- }
-
- private Object[] scanBlockScalarBreaks(int indent) {
- // See the specification for details.
- StringBuilder chunks = new StringBuilder();
- Mark endMark = reader.getMark();
- int ff = 0;
- int col = this.reader.getColumn();
- // Scan for up to the expected indentation-level of spaces, then move
- // forward past that amount.
- while (col < indent && reader.peek(ff) == ' ') {
- ff++;
- col++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- // Consume one or more line breaks followed by any amount of spaces,
- // until we find something that isn't a line-break.
- String lineBreak = null;
- while ((lineBreak = scanLineBreak()).length() != 0) {
- chunks.append(lineBreak);
- endMark = reader.getMark();
- // Scan past up to (indent) spaces on the next line, then forward
- // past them.
- ff = 0;
- col = this.reader.getColumn();
- while (col < indent && reader.peek(ff) == ' ') {
- ff++;
- col++;
- }
- if (ff > 0) {
- reader.forward(ff);
- }
- }
- // Return both the assembled intervening string and the end-mark.
- return new Object[] { chunks.toString(), endMark };
- }
+ // None of the above so safe to chomp the spaces.
+ return false;
+ }
- /**
- * Scan a flow-style scalar. Flow scalars are presented in one of two forms;
- * first, a flow scalar may be a double-quoted string; second, a flow scalar
- * may be a single-quoted string.
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#flow"></a> style/syntax
- *
- * <pre>
- * See the specification for details.
- * Note that we loose indentation rules for quoted scalars. Quoted
- * scalars don't need to adhere indentation because &quot; and ' clearly
- * mark the beginning and the end of them. Therefore we are less
- * restrictive then the specification requires. We only need to check
- * that document separators are not included in scalars.
- * </pre>
- */
- private Token scanFlowScalar(char style) {
- boolean _double;
- // The style will be either single- or double-quoted; we determine this
- // by the first character in the entry (supplied)
- if (style == '"') {
- _double = true;
- } else {
- _double = false;
- }
- StringBuilder chunks = new StringBuilder();
- Mark startMark = reader.getMark();
- char quote = reader.peek();
- reader.forward();
- chunks.append(scanFlowScalarNonSpaces(_double, startMark));
- while (reader.peek() != quote) {
- chunks.append(scanFlowScalarSpaces(startMark));
- chunks.append(scanFlowScalarNonSpaces(_double, startMark));
- }
- reader.forward();
- Mark endMark = reader.getMark();
- return new ScalarToken(chunks.toString(), false, startMark, endMark, style);
+ /**
+ * See the specification for details. SnakeYAML and libyaml allow tabs inside plain scalar
+ */
+ private String scanPlainSpaces() {
+ int length = 0;
+ while (reader.peek(length) == ' ' || reader.peek(length) == '\t') {
+ length++;
}
-
- /**
- * Scan some number of flow-scalar non-space characters.
- */
- private String scanFlowScalarNonSpaces(boolean doubleQuoted, Mark startMark) {
- // See the specification for details.
- StringBuilder chunks = new StringBuilder();
- while (true) {
- // Scan through any number of characters which are not: NUL, blank,
- // tabs, line breaks, single-quotes, double-quotes, or backslashes.
- int length = 0;
- while (Constant.NULL_BL_T_LINEBR.hasNo(reader.peek(length), "\'\"\\")) {
- length++;
- }
- if (length != 0) {
- chunks.append(reader.prefixForward(length));
- }
- // Depending on our quoting-type, the characters ', " and \ have
- // differing meanings.
- char ch = reader.peek();
- if (!doubleQuoted && ch == '\'' && reader.peek(1) == '\'') {
- chunks.append("'");
- reader.forward(2);
- } else if ((doubleQuoted && ch == '\'') || (!doubleQuoted && "\"\\".indexOf(ch) != -1)) {
- chunks.append(ch);
- reader.forward();
- } else if (doubleQuoted && ch == '\\') {
- reader.forward();
- ch = reader.peek();
- if (ESCAPE_REPLACEMENTS.containsKey(Character.valueOf(ch))) {
- // The character is one of the single-replacement
- // types; these are replaced with a literal character
- // from the mapping.
- chunks.append(ESCAPE_REPLACEMENTS.get(Character.valueOf(ch)));
- reader.forward();
- } else if (ESCAPE_CODES.containsKey(Character.valueOf(ch))) {
- // The character is a multi-digit escape sequence, with
- // length defined by the value in the ESCAPE_CODES map.
- length = ESCAPE_CODES.get(Character.valueOf(ch)).intValue();
- reader.forward();
- String hex = reader.prefix(length);
- if (NOT_HEXA.matcher(hex).find()) {
- throw new ScannerException("while scanning a double-quoted scalar",
- startMark, "expected escape sequence of " + length
- + " hexadecimal numbers, but found: " + hex,
- reader.getMark());
- }
- int decimal = Integer.parseInt(hex, 16);
- String unicode = new String(Character.toChars(decimal));
- chunks.append(unicode);
- reader.forward(length);
- } else if (scanLineBreak().length() != 0) {
- chunks.append(scanFlowScalarBreaks(startMark));
- } else {
- throw new ScannerException("while scanning a double-quoted scalar", startMark,
- "found unknown escape character " + ch + "(" + ((int) ch) + ")",
- reader.getMark());
- }
- } else {
- return chunks.toString();
- }
- }
- }
-
- private String scanFlowScalarSpaces(Mark startMark) {
- // See the specification for details.
- StringBuilder chunks = new StringBuilder();
- int length = 0;
- // Scan through any number of whitespace (space, tab) characters,
- // consuming them.
- while (" \t".indexOf(reader.peek(length)) != -1) {
- length++;
- }
- String whitespaces = reader.prefixForward(length);
- char ch = reader.peek();
- if (ch == '\0') {
- // A flow scalar cannot end with an end-of-stream
- throw new ScannerException("while scanning a quoted scalar", startMark,
- "found unexpected end of stream", reader.getMark());
- }
- // If we encounter a line break, scan it into our assembled string...
- String lineBreak = scanLineBreak();
- if (lineBreak.length() != 0) {
- String breaks = scanFlowScalarBreaks(startMark);
- if (!"\n".equals(lineBreak)) {
- chunks.append(lineBreak);
- } else if (breaks.length() == 0) {
- chunks.append(" ");
- }
- chunks.append(breaks);
+ String whitespaces = reader.prefixForward(length);
+ String lineBreak = scanLineBreak();
+ if (lineBreak.length() != 0) {
+ this.allowSimpleKey = true;
+ String prefix = reader.prefix(3);
+ if ("---".equals(prefix)
+ || "...".equals(prefix) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
+ return "";
+ }
+ if (parseComments && atEndOfPlain()) {
+ return "";
+ }
+ StringBuilder breaks = new StringBuilder();
+ while (true) {
+ if (reader.peek() == ' ') {
+ reader.forward();
} else {
- chunks.append(whitespaces);
- }
- return chunks.toString();
- }
-
- private String scanFlowScalarBreaks(Mark startMark) {
- // See the specification for details.
- StringBuilder chunks = new StringBuilder();
- while (true) {
- // Instead of checking indentation, we check for document
- // separators.
- String prefix = reader.prefix(3);
- if (("---".equals(prefix) || "...".equals(prefix))
- && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
- throw new ScannerException("while scanning a quoted scalar", startMark,
- "found unexpected document separator", reader.getMark());
- }
- // Scan past any number of spaces and tabs, ignoring them
- while (" \t".indexOf(reader.peek()) != -1) {
- reader.forward();
+ String lb = scanLineBreak();
+ if (lb.length() != 0) {
+ breaks.append(lb);
+ prefix = reader.prefix(3);
+ if ("---".equals(prefix)
+ || "...".equals(prefix) && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
+ return "";
}
- // If we stopped at a line break, add that; otherwise, return the
- // assembled set of scalar breaks.
- String lineBreak = scanLineBreak();
- if (lineBreak.length() != 0) {
- chunks.append(lineBreak);
- } else {
- return chunks.toString();
- }
- }
+ } else {
+ break;
+ }
+ }
+ }
+ if (!"\n".equals(lineBreak)) {
+ return lineBreak + breaks;
+ } else if (breaks.length() == 0) {
+ return " ";
+ }
+ return breaks.toString();
+ }
+ return whitespaces;
+ }
+
+ /**
+ * <p>
+ * Scan a Tag handle. A Tag handle takes one of three forms:
+ *
+ * <pre>
+ * "!" (c-primary-tag-handle)
+ * "!!" (ns-secondary-tag-handle)
+ * "!(name)!" (c-named-tag-handle)
+ * </pre>
+ *
+ * Where (name) must be formatted as an ns-word-char.
+ * </p>
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#c-tag-handle"></a>
+ * @see <a href="http://www.yaml.org/spec/1.1/#ns-word-char"></a>
+ *
+ * <pre>
+ * See the specification for details.
+ * For some strange reasons, the specification does not allow '_' in
+ * tag handles. I have allowed it anyway.
+ * </pre>
+ */
+ private String scanTagHandle(String name, Mark startMark) {
+ int c = reader.peek();
+ if (c != '!') {
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a " + name, startMark,
+ "expected '!', but found " + s + "(" + (c) + ")", reader.getMark());
+ }
+ // Look for the next '!' in the stream, stopping if we hit a
+ // non-word-character. If the first character is a space, then the
+ // tag-handle is a c-primary-tag-handle ('!').
+ int length = 1;
+ c = reader.peek(length);
+ if (c != ' ') {
+ // Scan through 0+ alphabetic characters.
+ // FIXME According to the specification, these should be
+ // ns-word-char only, which prohibits '_'. This might be a
+ // candidate for a configuration option.
+ while (Constant.ALPHA.has(c)) {
+ length++;
+ c = reader.peek(length);
+ }
+ // Found the next non-word-char. If this is not a space and not an
+ // '!', then this is an error, as the tag-handle was specified as:
+ // !(name) or similar; the trailing '!' is missing.
+ if (c != '!') {
+ reader.forward(length);
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a " + name, startMark,
+ "expected '!', but found " + s + "(" + (c) + ")", reader.getMark());
+ }
+ length++;
+ }
+ String value = reader.prefixForward(length);
+ return value;
+ }
+
+ /**
+ * <p>
+ * Scan a Tag URI. This scanning is valid for both local and global tag directives, because both
+ * appear to be valid URIs as far as scanning is concerned. The difference may be distinguished
+ * later, in parsing. This method will scan for ns-uri-char*, which covers both cases.
+ * </p>
+ *
+ * <p>
+ * This method performs no verification that the scanned URI conforms to any particular kind of
+ * URI specification.
+ * </p>
+ *
+ * @see <a href="http://www.yaml.org/spec/1.1/#ns-uri-char"></a>
+ */
+ private String scanTagUri(String name, Mark startMark) {
+ // See the specification for details.
+ // Note: we do not check if URI is well-formed.
+ StringBuilder chunks = new StringBuilder();
+ // Scan through accepted URI characters, which includes the standard
+ // URI characters, plus the start-escape character ('%'). When we get
+ // to a start-escape, scan the escaped sequence, then return.
+ int length = 0;
+ int c = reader.peek(length);
+ while (Constant.URI_CHARS.has(c)) {
+ if (c == '%') {
+ chunks.append(reader.prefixForward(length));
+ length = 0;
+ chunks.append(scanUriEscapes(name, startMark));
+ } else {
+ length++;
+ }
+ c = reader.peek(length);
+ }
+ // Consume the last "chunk", which would not otherwise be consumed by
+ // the loop above.
+ if (length != 0) {
+ chunks.append(reader.prefixForward(length));
+ }
+ if (chunks.length() == 0) {
+ // If no URI was found, an error has occurred.
+ final String s = String.valueOf(Character.toChars(c));
+ throw new ScannerException("while scanning a " + name, startMark,
+ "expected URI, but found " + s + "(" + (c) + ")", reader.getMark());
+ }
+ return chunks.toString();
+ }
+
+ /**
+ * <p>
+ * Scan a sequence of %-escaped URI escape codes and convert them into a String representing the
+ * unescaped values.
+ * </p>
+ *
+ * FIXME This method fails for more than 256 bytes' worth of URI-encoded characters in a row. Is
+ * this possible? Is this a use-case?
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2396.txt">section 2.4, Escaped Encoding</a>
+ */
+ private String scanUriEscapes(String name, Mark startMark) {
+ // First, look ahead to see how many URI-escaped characters we should
+ // expect, so we can use the correct buffer size.
+ int length = 1;
+ while (reader.peek(length * 3) == '%') {
+ length++;
+ }
+ // See the specification for details.
+ // URIs containing 16 and 32 bit Unicode characters are
+ // encoded in UTF-8, and then each octet is written as a
+ // separate character.
+ Mark beginningMark = reader.getMark();
+ ByteBuffer buff = ByteBuffer.allocate(length);
+ while (reader.peek() == '%') {
+ reader.forward();
+ try {
+ byte code = (byte) Integer.parseInt(reader.prefix(2), 16);
+ buff.put(code);
+ } catch (NumberFormatException nfe) {
+ int c1 = reader.peek();
+ final String s1 = String.valueOf(Character.toChars(c1));
+ int c2 = reader.peek(1);
+ final String s2 = String.valueOf(Character.toChars(c2));
+ throw new ScannerException("while scanning a " + name, startMark,
+ "expected URI escape sequence of 2 hexadecimal numbers, but found " + s1 + "(" + c1
+ + ") and " + s2 + "(" + c2 + ")",
+ reader.getMark());
+ }
+ reader.forward(2);
+ }
+ buff.flip();
+ try {
+ return UriEncoder.decode(buff);
+ } catch (CharacterCodingException e) {
+ throw new ScannerException("while scanning a " + name, startMark,
+ "expected URI in UTF-8: " + e.getMessage(), beginningMark);
+ }
+ }
+
+ /**
+ * Scan a line break, transforming:
+ *
+ * <pre>
+ * '\r\n' : '\n'
+ * '\r' : '\n'
+ * '\n' : '\n'
+ * '\x85' : '\n'
+ * default : ''
+ * </pre>
+ */
+ private String scanLineBreak() {
+ int c = reader.peek();
+ if (c == '\r' || c == '\n' || c == '\u0085') {
+ if (c == '\r' && '\n' == reader.peek(1)) {
+ reader.forward(2);
+ } else {
+ reader.forward();
+ }
+ return "\n";
+ } else if (c == '\u2028' || c == '\u2029') {
+ reader.forward();
+ return String.valueOf(Character.toChars(c));
}
+ return "";
+ }
- /**
- * Scan a plain scalar.
- *
- * <pre>
- * See the specification for details.
- * We add an additional restriction for the flow context:
- * plain scalars in the flow context cannot contain ',', ':' and '?'.
- * We also keep track of the `allow_simple_key` flag here.
- * Indentation rules are loosed for the flow context.
- * </pre>
- */
- private Token scanPlain() {
- StringBuilder chunks = new StringBuilder();
- Mark startMark = reader.getMark();
- Mark endMark = startMark;
- int indent = this.indent + 1;
- String spaces = "";
- while (true) {
- char ch;
- int length = 0;
- // A comment indicates the end of the scalar.
- if (reader.peek() == '#') {
- break;
- }
- while (true) {
- ch = reader.peek(length);
- if (Constant.NULL_BL_T_LINEBR.has(ch)
- || (this.flowLevel == 0 && ch == ':' && Constant.NULL_BL_T_LINEBR
- .has(reader.peek(length + 1)))
- || (this.flowLevel != 0 && ",:?[]{}".indexOf(ch) != -1)) {
- break;
- }
- length++;
- }
- // It's not clear what we should do with ':' in the flow context.
- if (this.flowLevel != 0 && ch == ':'
- && Constant.NULL_BL_T_LINEBR.hasNo(reader.peek(length + 1), ",[]{}")) {
- reader.forward(length);
- throw new ScannerException("while scanning a plain scalar", startMark,
- "found unexpected ':'", reader.getMark(),
- "Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.");
- }
- if (length == 0) {
- break;
- }
- this.allowSimpleKey = false;
- chunks.append(spaces);
- chunks.append(reader.prefixForward(length));
- endMark = reader.getMark();
- spaces = scanPlainSpaces();
- // System.out.printf("spaces[%s]\n", spaces);
- if (spaces.length() == 0 || reader.peek() == '#'
- || (this.flowLevel == 0 && this.reader.getColumn() < indent)) {
- break;
- }
- }
- return new ScalarToken(chunks.toString(), startMark, endMark, true);
+ private List<Token> makeTokenList(Token... tokens) {
+ List<Token> tokenList = new ArrayList<>();
+ for (int ix = 0; ix < tokens.length; ix++) {
+ if (tokens[ix] == null) {
+ continue;
+ }
+ if (!parseComments && (tokens[ix] instanceof CommentToken)) {
+ continue;
+ }
+ tokenList.add(tokens[ix]);
}
+ return tokenList;
+ }
- /**
- * See the specification for details. SnakeYAML and libyaml allow tabs
- * inside plain scalar
- */
- private String scanPlainSpaces() {
- int length = 0;
- while (reader.peek(length) == ' ' || reader.peek(length) == '\t') {
- length++;
- }
- String whitespaces = reader.prefixForward(length);
- String lineBreak = scanLineBreak();
- if (lineBreak.length() != 0) {
- this.allowSimpleKey = true;
- String prefix = reader.prefix(3);
- if ("---".equals(prefix) || "...".equals(prefix)
- && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
- return "";
- }
- StringBuilder breaks = new StringBuilder();
- while (true) {
- if (reader.peek() == ' ') {
- reader.forward();
- } else {
- String lb = scanLineBreak();
- if (lb.length() != 0) {
- breaks.append(lb);
- prefix = reader.prefix(3);
- if ("---".equals(prefix) || "...".equals(prefix)
- && Constant.NULL_BL_T_LINEBR.has(reader.peek(3))) {
- return "";
- }
- } else {
- break;
- }
- }
- }
- if (!"\n".equals(lineBreak)) {
- return lineBreak + breaks;
- } else if (breaks.length() == 0) {
- return " ";
- }
- return breaks.toString();
- }
- return whitespaces;
- }
+ /**
+ * Chomping the tail may have 3 values - yes, no, not defined.
+ */
+ private static class Chomping {
- /**
- * <p>
- * Scan a Tag handle. A Tag handle takes one of three forms:
- *
- * <pre>
- * "!" (c-primary-tag-handle)
- * "!!" (ns-secondary-tag-handle)
- * "!(name)!" (c-named-tag-handle)
- * </pre>
- *
- * Where (name) must be formatted as an ns-word-char.
- * </p>
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#c-tag-handle"></a>
- * @see <a href="http://www.yaml.org/spec/1.1/#ns-word-char"></a>
- *
- * <pre>
- * See the specification for details.
- * For some strange reasons, the specification does not allow '_' in
- * tag handles. I have allowed it anyway.
- * </pre>
- */
- private String scanTagHandle(String name, Mark startMark) {
- char ch = reader.peek();
- if (ch != '!') {
- throw new ScannerException("while scanning a " + name, startMark,
- "expected '!', but found " + ch + "(" + ((int) ch) + ")", reader.getMark());
- }
- // Look for the next '!' in the stream, stopping if we hit a
- // non-word-character. If the first character is a space, then the
- // tag-handle is a c-primary-tag-handle ('!').
- int length = 1;
- ch = reader.peek(length);
- if (ch != ' ') {
- // Scan through 0+ alphabetic characters.
- // FIXME According to the specification, these should be
- // ns-word-char only, which prohibits '_'. This might be a
- // candidate for a configuration option.
- while (Constant.ALPHA.has(ch)) {
- length++;
- ch = reader.peek(length);
- }
- // Found the next non-word-char. If this is not a space and not an
- // '!', then this is an error, as the tag-handle was specified as:
- // !(name) or similar; the trailing '!' is missing.
- if (ch != '!') {
- reader.forward(length);
- throw new ScannerException("while scanning a " + name, startMark,
- "expected '!', but found " + ch + "(" + ((int) ch) + ")", reader.getMark());
- }
- length++;
- }
- String value = reader.prefixForward(length);
- return value;
- }
+ private final Boolean value;
+ private final int increment;
- /**
- * <p>
- * Scan a Tag URI. This scanning is valid for both local and global tag
- * directives, because both appear to be valid URIs as far as scanning is
- * concerned. The difference may be distinguished later, in parsing. This
- * method will scan for ns-uri-char*, which covers both cases.
- * </p>
- *
- * <p>
- * This method performs no verification that the scanned URI conforms to any
- * particular kind of URI specification.
- * </p>
- *
- * @see <a href="http://www.yaml.org/spec/1.1/#ns-uri-char"></a>
- */
- private String scanTagUri(String name, Mark startMark) {
- // See the specification for details.
- // Note: we do not check if URI is well-formed.
- StringBuilder chunks = new StringBuilder();
- // Scan through accepted URI characters, which includes the standard
- // URI characters, plus the start-escape character ('%'). When we get
- // to a start-escape, scan the escaped sequence, then return.
- int length = 0;
- char ch = reader.peek(length);
- while (Constant.URI_CHARS.has(ch)) {
- if (ch == '%') {
- chunks.append(reader.prefixForward(length));
- length = 0;
- chunks.append(scanUriEscapes(name, startMark));
- } else {
- length++;
- }
- ch = reader.peek(length);
- }
- // Consume the last "chunk", which would not otherwise be consumed by
- // the loop above.
- if (length != 0) {
- chunks.append(reader.prefixForward(length));
- length = 0;
- }
- if (chunks.length() == 0) {
- // If no URI was found, an error has occurred.
- throw new ScannerException("while scanning a " + name, startMark,
- "expected URI, but found " + ch + "(" + ((int) ch) + ")", reader.getMark());
- }
- return chunks.toString();
+ public Chomping(Boolean value, int increment) {
+ this.value = value;
+ this.increment = increment;
}
- /**
- * <p>
- * Scan a sequence of %-escaped URI escape codes and convert them into a
- * String representing the unescaped values.
- * </p>
- *
- * FIXME This method fails for more than 256 bytes' worth of URI-encoded
- * characters in a row. Is this possible? Is this a use-case?
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2396.txt"></a>, section 2.4, Escaped Encoding.
- */
- private String scanUriEscapes(String name, Mark startMark) {
- // First, look ahead to see how many URI-escaped characters we should
- // expect, so we can use the correct buffer size.
- int length = 1;
- while (reader.peek(length * 3) == '%') {
- length++;
- }
- // See the specification for details.
- // URIs containing 16 and 32 bit Unicode characters are
- // encoded in UTF-8, and then each octet is written as a
- // separate character.
- Mark beginningMark = reader.getMark();
- ByteBuffer buff = ByteBuffer.allocate(length);
- while (reader.peek() == '%') {
- reader.forward();
- try {
- byte code = (byte) Integer.parseInt(reader.prefix(2), 16);
- buff.put(code);
- } catch (NumberFormatException nfe) {
- throw new ScannerException("while scanning a " + name, startMark,
- "expected URI escape sequence of 2 hexadecimal numbers, but found "
- + reader.peek() + "(" + ((int) reader.peek()) + ") and "
- + reader.peek(1) + "(" + ((int) reader.peek(1)) + ")",
- reader.getMark());
- }
- reader.forward(2);
- }
- buff.flip();
- try {
- return UriEncoder.decode(buff);
- } catch (CharacterCodingException e) {
- throw new ScannerException("while scanning a " + name, startMark,
- "expected URI in UTF-8: " + e.getMessage(), beginningMark);
- }
+ public boolean chompTailIsNotFalse() {
+ return value == null || value;
}
- /**
- * Scan a line break, transforming:
- *
- * <pre>
- * '\r\n' : '\n'
- * '\r' : '\n'
- * '\n' : '\n'
- * '\x85' : '\n'
- * default : ''
- * </pre>
- */
- private String scanLineBreak() {
- // Transforms:
- // '\r\n' : '\n'
- // '\r' : '\n'
- // '\n' : '\n'
- // '\x85' : '\n'
- // default : ''
- char ch = reader.peek();
- if (ch == '\r' || ch == '\n' || ch == '\u0085') {
- if (ch == '\r' && '\n' == reader.peek(1)) {
- reader.forward(2);
- } else {
- reader.forward();
- }
- return "\n";
- } else if (ch == '\u2028' || ch == '\u2029') {
- reader.forward();
- return String.valueOf(ch);
- }
- return "";
+ public boolean chompTailIsTrue() {
+ return value != null && value;
}
- /**
- * Chomping the tail may have 3 values - yes, no, not defined.
- */
- private static class Chomping {
- private final Boolean value;
- private final int increment;
-
- public Chomping(Boolean value, int increment) {
- this.value = value;
- this.increment = increment;
- }
-
- public boolean chompTailIsNotFalse() {
- return value == null || value;
- }
-
- public boolean chompTailIsTrue() {
- return value != null && value;
- }
-
- public int getIncrement() {
- return increment;
- }
+ public int getIncrement() {
+ return increment;
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/scanner/SimpleKey.java b/src/main/java/org/yaml/snakeyaml/scanner/SimpleKey.java
index 3fe710cc..931c4e03 100644
--- a/src/main/java/org/yaml/snakeyaml/scanner/SimpleKey.java
+++ b/src/main/java/org/yaml/snakeyaml/scanner/SimpleKey.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.scanner;
@@ -22,53 +20,54 @@ import org.yaml.snakeyaml.error.Mark;
* <p>
* Helper class for {@link ScannerImpl}.
* </p>
- *
+ *
* @see ScannerImpl
*/
final class SimpleKey {
- private int tokenNumber;
- private boolean required;
- private int index;
- private int line;
- private int column;
- private Mark mark;
- public SimpleKey(int tokenNumber, boolean required, int index, int line, int column, Mark mark) {
- this.tokenNumber = tokenNumber;
- this.required = required;
- this.index = index;
- this.line = line;
- this.column = column;
- this.mark = mark;
- }
+ private final int tokenNumber;
+ private final boolean required;
+ private final int index;
+ private final int line;
+ private final int column;
+ private final Mark mark;
+
+ public SimpleKey(int tokenNumber, boolean required, int index, int line, int column, Mark mark) {
+ this.tokenNumber = tokenNumber;
+ this.required = required;
+ this.index = index;
+ this.line = line;
+ this.column = column;
+ this.mark = mark;
+ }
- public int getTokenNumber() {
- return this.tokenNumber;
- }
+ public int getTokenNumber() {
+ return this.tokenNumber;
+ }
- public int getColumn() {
- return this.column;
- }
+ public int getColumn() {
+ return this.column;
+ }
- public Mark getMark() {
- return mark;
- }
+ public Mark getMark() {
+ return mark;
+ }
- public int getIndex() {
- return index;
- }
+ public int getIndex() {
+ return index;
+ }
- public int getLine() {
- return line;
- }
+ public int getLine() {
+ return line;
+ }
- public boolean isRequired() {
- return required;
- }
+ public boolean isRequired() {
+ return required;
+ }
- @Override
- public String toString() {
- return "SimpleKey - tokenNumber=" + tokenNumber + " required=" + required + " index="
- + index + " line=" + line + " column=" + column;
- }
+ @Override
+ public String toString() {
+ return "SimpleKey - tokenNumber=" + tokenNumber + " required=" + required + " index=" + index
+ + " line=" + line + " column=" + column;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/serializer/AnchorGenerator.java b/src/main/java/org/yaml/snakeyaml/serializer/AnchorGenerator.java
index 2308eb59..6674901e 100644
--- a/src/main/java/org/yaml/snakeyaml/serializer/AnchorGenerator.java
+++ b/src/main/java/org/yaml/snakeyaml/serializer/AnchorGenerator.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.serializer;
@@ -19,5 +17,5 @@ import org.yaml.snakeyaml.nodes.Node;
public interface AnchorGenerator {
- String nextAnchor(Node node);
+ String nextAnchor(Node node);
}
diff --git a/src/main/java/org/yaml/snakeyaml/serializer/NumberAnchorGenerator.java b/src/main/java/org/yaml/snakeyaml/serializer/NumberAnchorGenerator.java
index 2f316f88..b33ad9a1 100644
--- a/src/main/java/org/yaml/snakeyaml/serializer/NumberAnchorGenerator.java
+++ b/src/main/java/org/yaml/snakeyaml/serializer/NumberAnchorGenerator.java
@@ -1,39 +1,36 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.serializer;
-import org.yaml.snakeyaml.nodes.Node;
-
import java.text.NumberFormat;
+import org.yaml.snakeyaml.nodes.Node;
public class NumberAnchorGenerator implements AnchorGenerator {
- private int lastAnchorId = 0;
+ private int lastAnchorId = 0;
- public NumberAnchorGenerator(int lastAnchorId) {
- this.lastAnchorId = lastAnchorId;
- }
+ public NumberAnchorGenerator(int lastAnchorId) {
+ this.lastAnchorId = lastAnchorId;
+ }
- public String nextAnchor(Node node) {
- this.lastAnchorId++;
- NumberFormat format = NumberFormat.getNumberInstance();
- format.setMinimumIntegerDigits(3);
- format.setMaximumFractionDigits(0);// issue 172
- format.setGroupingUsed(false);
- String anchorId = format.format(this.lastAnchorId);
- return "id" + anchorId;
- }
+ public String nextAnchor(Node node) {
+ this.lastAnchorId++;
+ NumberFormat format = NumberFormat.getNumberInstance();
+ format.setMinimumIntegerDigits(3);
+ format.setMaximumFractionDigits(0);// issue 172
+ format.setGroupingUsed(false);
+ String anchorId = format.format(this.lastAnchorId);
+ return "id" + anchorId;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java b/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java
index 2decf3f3..7d394308 100644
--- a/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java
+++ b/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java
@@ -1,32 +1,30 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.serializer;
import java.io.IOException;
-import java.text.NumberFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.Version;
+import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.emitter.Emitable;
import org.yaml.snakeyaml.events.AliasEvent;
+import org.yaml.snakeyaml.events.CommentEvent;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.ImplicitTuple;
@@ -38,7 +36,6 @@ import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.events.StreamEndEvent;
import org.yaml.snakeyaml.events.StreamStartEvent;
import org.yaml.snakeyaml.nodes.AnchorNode;
-import org.yaml.snakeyaml.nodes.CollectionNode;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
@@ -49,153 +46,181 @@ import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.resolver.Resolver;
public final class Serializer {
- private final Emitable emitter;
- private final Resolver resolver;
- private boolean explicitStart;
- private boolean explicitEnd;
- private Version useVersion;
- private Map<String, String> useTags;
- private Set<Node> serializedNodes;
- private Map<Node, String> anchors;
- private AnchorGenerator anchorGenerator;
- private Boolean closed;
- private Tag explicitRoot;
- public Serializer(Emitable emitter, Resolver resolver, DumperOptions opts, Tag rootTag) {
- this.emitter = emitter;
- this.resolver = resolver;
- this.explicitStart = opts.isExplicitStart();
- this.explicitEnd = opts.isExplicitEnd();
- if (opts.getVersion() != null) {
- this.useVersion = opts.getVersion();
- }
- this.useTags = opts.getTags();
- this.serializedNodes = new HashSet<Node>();
- this.anchors = new HashMap<Node, String>();
- this.anchorGenerator = opts.getAnchorGenerator();
- this.closed = null;
- this.explicitRoot = rootTag;
+ private final Emitable emitter;
+ private final Resolver resolver;
+ private final boolean explicitStart;
+ private final boolean explicitEnd;
+ private Version useVersion;
+ private final Map<String, String> useTags;
+ private final Set<Node> serializedNodes;
+ private final Map<Node, String> anchors;
+ private final AnchorGenerator anchorGenerator;
+ private Boolean closed;
+ private final Tag explicitRoot;
+
+ public Serializer(Emitable emitter, Resolver resolver, DumperOptions opts, Tag rootTag) {
+ this.emitter = emitter;
+ this.resolver = resolver;
+ this.explicitStart = opts.isExplicitStart();
+ this.explicitEnd = opts.isExplicitEnd();
+ if (opts.getVersion() != null) {
+ this.useVersion = opts.getVersion();
}
+ this.useTags = opts.getTags();
+ this.serializedNodes = new HashSet<Node>();
+ this.anchors = new HashMap<Node, String>();
+ this.anchorGenerator = opts.getAnchorGenerator();
+ this.closed = null;
+ this.explicitRoot = rootTag;
+ }
- public void open() throws IOException {
- if (closed == null) {
- this.emitter.emit(new StreamStartEvent(null, null));
- this.closed = Boolean.FALSE;
- } else if (Boolean.TRUE.equals(closed)) {
- throw new SerializerException("serializer is closed");
- } else {
- throw new SerializerException("serializer is already opened");
- }
+ public void open() throws IOException {
+ if (closed == null) {
+ this.emitter.emit(new StreamStartEvent(null, null));
+ this.closed = Boolean.FALSE;
+ } else if (Boolean.TRUE.equals(closed)) {
+ throw new SerializerException("serializer is closed");
+ } else {
+ throw new SerializerException("serializer is already opened");
}
+ }
- public void close() throws IOException {
- if (closed == null) {
- throw new SerializerException("serializer is not opened");
- } else if (!Boolean.TRUE.equals(closed)) {
- this.emitter.emit(new StreamEndEvent(null, null));
- this.closed = Boolean.TRUE;
- }
+ public void close() throws IOException {
+ if (closed == null) {
+ throw new SerializerException("serializer is not opened");
+ } else if (!Boolean.TRUE.equals(closed)) {
+ this.emitter.emit(new StreamEndEvent(null, null));
+ this.closed = Boolean.TRUE;
+ // release unused resources
+ this.serializedNodes.clear();
+ this.anchors.clear();
}
+ }
- public void serialize(Node node) throws IOException {
- if (closed == null) {
- throw new SerializerException("serializer is not opened");
- } else if (closed) {
- throw new SerializerException("serializer is closed");
- }
- this.emitter.emit(new DocumentStartEvent(null, null, this.explicitStart, this.useVersion,
- useTags));
- anchorNode(node);
- if (explicitRoot != null) {
- node.setTag(explicitRoot);
- }
- serializeNode(node, null);
- this.emitter.emit(new DocumentEndEvent(null, null, this.explicitEnd));
- this.serializedNodes.clear();
- this.anchors.clear();
+ public void serialize(Node node) throws IOException {
+ if (closed == null) {
+ throw new SerializerException("serializer is not opened");
+ } else if (closed) {
+ throw new SerializerException("serializer is closed");
+ }
+ this.emitter
+ .emit(new DocumentStartEvent(null, null, this.explicitStart, this.useVersion, useTags));
+ anchorNode(node);
+ if (explicitRoot != null) {
+ node.setTag(explicitRoot);
}
+ serializeNode(node, null);
+ this.emitter.emit(new DocumentEndEvent(null, null, this.explicitEnd));
+ this.serializedNodes.clear();
+ this.anchors.clear();
+ }
- private void anchorNode(Node node) {
- if (node.getNodeId() == NodeId.anchor) {
- node = ((AnchorNode) node).getRealNode();
- }
- if (this.anchors.containsKey(node)) {
- String anchor = this.anchors.get(node);
- if (null == anchor) {
- anchor = this.anchorGenerator.nextAnchor(node);
- this.anchors.put(node, anchor);
- }
- } else {
- this.anchors.put(node, null);
- switch (node.getNodeId()) {
- case sequence:
- SequenceNode seqNode = (SequenceNode) node;
- List<Node> list = seqNode.getValue();
- for (Node item : list) {
- anchorNode(item);
- }
- break;
- case mapping:
- MappingNode mnode = (MappingNode) node;
- List<NodeTuple> map = mnode.getValue();
- for (NodeTuple object : map) {
- Node key = object.getKeyNode();
- Node value = object.getValueNode();
- anchorNode(key);
- anchorNode(value);
- }
- break;
- }
- }
+ private void anchorNode(Node node) {
+ if (node.getNodeId() == NodeId.anchor) {
+ node = ((AnchorNode) node).getRealNode();
}
+ if (this.anchors.containsKey(node)) {
+ String anchor = this.anchors.get(node);
+ if (null == anchor) {
+ anchor = this.anchorGenerator.nextAnchor(node);
+ this.anchors.put(node, anchor);
+ }
+ } else {
+ this.anchors.put(node,
+ node.getAnchor() != null ? this.anchorGenerator.nextAnchor(node) : null);
+ switch (node.getNodeId()) {
+ case sequence:
+ SequenceNode seqNode = (SequenceNode) node;
+ List<Node> list = seqNode.getValue();
+ for (Node item : list) {
+ anchorNode(item);
+ }
+ break;
+ case mapping:
+ MappingNode mnode = (MappingNode) node;
+ List<NodeTuple> map = mnode.getValue();
+ for (NodeTuple object : map) {
+ Node key = object.getKeyNode();
+ Node value = object.getValueNode();
+ anchorNode(key);
+ anchorNode(value);
+ }
+ break;
+ }
+ }
+ }
- private void serializeNode(Node node, Node parent) throws IOException {
- if (node.getNodeId() == NodeId.anchor) {
- node = ((AnchorNode) node).getRealNode();
- }
- String tAlias = this.anchors.get(node);
- if (this.serializedNodes.contains(node)) {
- this.emitter.emit(new AliasEvent(tAlias, null, null));
- } else {
- this.serializedNodes.add(node);
- switch (node.getNodeId()) {
- case scalar:
- ScalarNode scalarNode = (ScalarNode) node;
- Tag detectedTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), true);
- Tag defaultTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), false);
- ImplicitTuple tuple = new ImplicitTuple(node.getTag().equals(detectedTag), node
- .getTag().equals(defaultTag));
- ScalarEvent event = new ScalarEvent(tAlias, node.getTag().getValue(), tuple,
- scalarNode.getValue(), null, null, scalarNode.getStyle());
- this.emitter.emit(event);
- break;
- case sequence:
- SequenceNode seqNode = (SequenceNode) node;
- boolean implicitS = node.getTag().equals(this.resolver.resolve(NodeId.sequence,
- null, true));
- this.emitter.emit(new SequenceStartEvent(tAlias, node.getTag().getValue(),
- implicitS, null, null, seqNode.getFlowStyle()));
- List<Node> list = seqNode.getValue();
- for (Node item : list) {
- serializeNode(item, node);
- }
- this.emitter.emit(new SequenceEndEvent(null, null));
- break;
- default:// instance of MappingNode
- Tag implicitTag = this.resolver.resolve(NodeId.mapping, null, true);
- boolean implicitM = node.getTag().equals(implicitTag);
- this.emitter.emit(new MappingStartEvent(tAlias, node.getTag().getValue(),
- implicitM, null, null, ((CollectionNode) node).getFlowStyle()));
- MappingNode mnode = (MappingNode) node;
- List<NodeTuple> map = mnode.getValue();
- for (NodeTuple row : map) {
- Node key = row.getKeyNode();
- Node value = row.getValueNode();
- serializeNode(key, mnode);
- serializeNode(value, mnode);
- }
- this.emitter.emit(new MappingEndEvent(null, null));
+ // parent Node is not used but might be used in the future
+ private void serializeNode(Node node, Node parent) throws IOException {
+ if (node.getNodeId() == NodeId.anchor) {
+ node = ((AnchorNode) node).getRealNode();
+ }
+ String tAlias = this.anchors.get(node);
+ if (this.serializedNodes.contains(node)) {
+ this.emitter.emit(new AliasEvent(tAlias, null, null));
+ } else {
+ this.serializedNodes.add(node);
+ switch (node.getNodeId()) {
+ case scalar:
+ ScalarNode scalarNode = (ScalarNode) node;
+ serializeComments(node.getBlockComments());
+ Tag detectedTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), true);
+ Tag defaultTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), false);
+ ImplicitTuple tuple = new ImplicitTuple(node.getTag().equals(detectedTag),
+ node.getTag().equals(defaultTag));
+ ScalarEvent event = new ScalarEvent(tAlias, node.getTag().getValue(), tuple,
+ scalarNode.getValue(), null, null, scalarNode.getScalarStyle());
+ this.emitter.emit(event);
+ serializeComments(node.getInLineComments());
+ serializeComments(node.getEndComments());
+ break;
+ case sequence:
+ SequenceNode seqNode = (SequenceNode) node;
+ serializeComments(node.getBlockComments());
+ boolean implicitS =
+ node.getTag().equals(this.resolver.resolve(NodeId.sequence, null, true));
+ this.emitter.emit(new SequenceStartEvent(tAlias, node.getTag().getValue(), implicitS,
+ null, null, seqNode.getFlowStyle()));
+ List<Node> list = seqNode.getValue();
+ for (Node item : list) {
+ serializeNode(item, node);
+ }
+ this.emitter.emit(new SequenceEndEvent(null, null));
+ serializeComments(node.getInLineComments());
+ serializeComments(node.getEndComments());
+ break;
+ default:// instance of MappingNode
+ serializeComments(node.getBlockComments());
+ Tag implicitTag = this.resolver.resolve(NodeId.mapping, null, true);
+ boolean implicitM = node.getTag().equals(implicitTag);
+ MappingNode mnode = (MappingNode) node;
+ List<NodeTuple> map = mnode.getValue();
+ if (mnode.getTag() != Tag.COMMENT) {
+ this.emitter.emit(new MappingStartEvent(tAlias, mnode.getTag().getValue(), implicitM,
+ null, null, mnode.getFlowStyle()));
+ for (NodeTuple row : map) {
+ Node key = row.getKeyNode();
+ Node value = row.getValueNode();
+ serializeNode(key, mnode);
+ serializeNode(value, mnode);
}
- }
+ this.emitter.emit(new MappingEndEvent(null, null));
+ serializeComments(node.getInLineComments());
+ serializeComments(node.getEndComments());
+ }
+ }
+ }
+ }
+
+ private void serializeComments(List<CommentLine> comments) throws IOException {
+ if (comments == null) {
+ return;
+ }
+ for (CommentLine line : comments) {
+ CommentEvent commentEvent = new CommentEvent(line.getCommentType(), line.getValue(),
+ line.getStartMark(), line.getEndMark());
+ this.emitter.emit(commentEvent);
}
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/serializer/SerializerException.java b/src/main/java/org/yaml/snakeyaml/serializer/SerializerException.java
index 0cb6e887..21e9f38f 100644
--- a/src/main/java/org/yaml/snakeyaml/serializer/SerializerException.java
+++ b/src/main/java/org/yaml/snakeyaml/serializer/SerializerException.java
@@ -1,26 +1,25 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.serializer;
import org.yaml.snakeyaml.error.YAMLException;
public class SerializerException extends YAMLException {
- private static final long serialVersionUID = 2632638197498912433L;
- public SerializerException(String message) {
- super(message);
- }
+ private static final long serialVersionUID = 2632638197498912433L;
+
+ public SerializerException(String message) {
+ super(message);
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/AliasToken.java b/src/main/java/org/yaml/snakeyaml/tokens/AliasToken.java
index df2ee2e9..be8a5144 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/AliasToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/AliasToken.java
@@ -1,41 +1,35 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
import org.yaml.snakeyaml.error.Mark;
public final class AliasToken extends Token {
- private final String value;
- public AliasToken(String value, Mark startMark, Mark endMark) {
- super(startMark, endMark);
- this.value = value;
- }
+ private final String value;
- public String getValue() {
- return this.value;
- }
+ public AliasToken(String value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ this.value = value;
+ }
- @Override
- protected String getArguments() {
- return "value=" + value;
- }
+ public String getValue() {
+ return this.value;
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Alias;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Alias;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/AnchorToken.java b/src/main/java/org/yaml/snakeyaml/tokens/AnchorToken.java
index 3629eeae..6d10cf71 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/AnchorToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/AnchorToken.java
@@ -1,41 +1,35 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
import org.yaml.snakeyaml.error.Mark;
public final class AnchorToken extends Token {
- private final String value;
- public AnchorToken(String value, Mark startMark, Mark endMark) {
- super(startMark, endMark);
- this.value = value;
- }
+ private final String value;
- public String getValue() {
- return this.value;
- }
+ public AnchorToken(String value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ this.value = value;
+ }
- @Override
- protected String getArguments() {
- return "value=" + value;
- }
+ public String getValue() {
+ return this.value;
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Anchor;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Anchor;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/BlockEndToken.java b/src/main/java/org/yaml/snakeyaml/tokens/BlockEndToken.java
index 3315bc40..0f994154 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/BlockEndToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/BlockEndToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class BlockEndToken extends Token {
- public BlockEndToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public BlockEndToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.BlockEnd;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.BlockEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/BlockEntryToken.java b/src/main/java/org/yaml/snakeyaml/tokens/BlockEntryToken.java
index 574445f0..3657ae70 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/BlockEntryToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/BlockEntryToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class BlockEntryToken extends Token {
- public BlockEntryToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public BlockEntryToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.BlockEntry;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.BlockEntry;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/BlockMappingStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/BlockMappingStartToken.java
index 95a61642..4a35c917 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/BlockMappingStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/BlockMappingStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class BlockMappingStartToken extends Token {
- public BlockMappingStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public BlockMappingStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.BlockMappingStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.BlockMappingStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/BlockSequenceStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/BlockSequenceStartToken.java
index d70194c7..3ea7a20d 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/BlockSequenceStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/BlockSequenceStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class BlockSequenceStartToken extends Token {
- public BlockSequenceStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public BlockSequenceStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.BlockSequenceStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.BlockSequenceStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java b/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java
index 12c067ef..36fe079e 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java
@@ -1,29 +1,45 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
+import java.util.Objects;
+import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.Mark;
-public class CommentToken extends Token {
- public CommentToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+public final class CommentToken extends Token {
- @Override
- public ID getTokenId() {
- return ID.Comment;
- }
+ private final CommentType type;
+ private final String value;
+
+ public CommentToken(CommentType type, String value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ Objects.requireNonNull(type);
+ this.type = type;
+ Objects.requireNonNull(value);
+ this.value = value;
+ }
+
+ public CommentType getCommentType() {
+ return this.type;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Comment;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/DirectiveToken.java b/src/main/java/org/yaml/snakeyaml/tokens/DirectiveToken.java
index af1743f8..d6d53035 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/DirectiveToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/DirectiveToken.java
@@ -1,58 +1,46 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
import java.util.List;
-
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
public final class DirectiveToken<T> extends Token {
- private final String name;
- private final List<T> value;
- public DirectiveToken(String name, List<T> value, Mark startMark, Mark endMark) {
- super(startMark, endMark);
- this.name = name;
- if (value != null && value.size() != 2) {
- throw new YAMLException("Two strings must be provided instead of "
- + String.valueOf(value.size()));
- }
- this.value = value;
- }
+ private final String name;
+ private final List<T> value;
- public String getName() {
- return this.name;
+ public DirectiveToken(String name, List<T> value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ this.name = name;
+ if (value != null && value.size() != 2) {
+ throw new YAMLException("Two strings must be provided instead of " + value.size());
}
+ this.value = value;
+ }
- public List<T> getValue() {
- return this.value;
- }
+ public String getName() {
+ return this.name;
+ }
- @Override
- protected String getArguments() {
- if (value != null) {
- return "name=" + name + ", value=[" + value.get(0) + ", " + value.get(1) + "]";
- } else {
- return "name=" + name;
- }
- }
+ public List<T> getValue() {
+ return this.value;
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Directive;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Directive;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/DocumentEndToken.java b/src/main/java/org/yaml/snakeyaml/tokens/DocumentEndToken.java
index ee17dab1..4c2319c3 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/DocumentEndToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/DocumentEndToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class DocumentEndToken extends Token {
- public DocumentEndToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public DocumentEndToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.DocumentEnd;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.DocumentEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/DocumentStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/DocumentStartToken.java
index 0b72deb3..1ce5445d 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/DocumentStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/DocumentStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class DocumentStartToken extends Token {
- public DocumentStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public DocumentStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.DocumentStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.DocumentStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/FlowEntryToken.java b/src/main/java/org/yaml/snakeyaml/tokens/FlowEntryToken.java
index b1afb0f5..03e5c9ff 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/FlowEntryToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/FlowEntryToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class FlowEntryToken extends Token {
- public FlowEntryToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public FlowEntryToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.FlowEntry;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.FlowEntry;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingEndToken.java b/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingEndToken.java
index 1659a9f0..8ec5dd4c 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingEndToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingEndToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class FlowMappingEndToken extends Token {
- public FlowMappingEndToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public FlowMappingEndToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.FlowMappingEnd;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.FlowMappingEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingStartToken.java
index 5a984c72..8ee9814b 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/FlowMappingStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class FlowMappingStartToken extends Token {
- public FlowMappingStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public FlowMappingStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.FlowMappingStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.FlowMappingStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceEndToken.java b/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceEndToken.java
index 39b03c43..cc991d42 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceEndToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceEndToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class FlowSequenceEndToken extends Token {
- public FlowSequenceEndToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public FlowSequenceEndToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.FlowSequenceEnd;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.FlowSequenceEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceStartToken.java
index da89785e..1e2de015 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/FlowSequenceStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class FlowSequenceStartToken extends Token {
- public FlowSequenceStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public FlowSequenceStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.FlowSequenceStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.FlowSequenceStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/KeyToken.java b/src/main/java/org/yaml/snakeyaml/tokens/KeyToken.java
index 0f880438..2b36521c 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/KeyToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/KeyToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class KeyToken extends Token {
- public KeyToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public KeyToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Key;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Key;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/ScalarToken.java b/src/main/java/org/yaml/snakeyaml/tokens/ScalarToken.java
index 828189e8..5a6ccd51 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/ScalarToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/ScalarToken.java
@@ -1,57 +1,56 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
+import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.error.Mark;
public final class ScalarToken extends Token {
- private final String value;
- private final boolean plain;
- private final char style;
- public ScalarToken(String value, Mark startMark, Mark endMark, boolean plain) {
- this(value, plain, startMark, endMark, (char) 0);
+ private final String value;
+ private final boolean plain;
+ private final DumperOptions.ScalarStyle style;
+
+ public ScalarToken(String value, Mark startMark, Mark endMark, boolean plain) {
+ this(value, plain, startMark, endMark, DumperOptions.ScalarStyle.PLAIN);
+ }
+
+ public ScalarToken(String value, boolean plain, Mark startMark, Mark endMark,
+ DumperOptions.ScalarStyle style) {
+ super(startMark, endMark);
+ this.value = value;
+ this.plain = plain;
+ if (style == null) {
+ throw new NullPointerException("Style must be provided.");
}
+ this.style = style;
+ }
- public ScalarToken(String value, boolean plain, Mark startMark, Mark endMark, char style) {
- super(startMark, endMark);
- this.value = value;
- this.plain = plain;
- this.style = style;
- }
-
- public boolean getPlain() {
- return this.plain;
- }
-
- public String getValue() {
- return this.value;
- }
+ public boolean getPlain() {
+ return this.plain;
+ }
- public char getStyle() {
- return this.style;
- }
+ public String getValue() {
+ return this.value;
+ }
- @Override
- protected String getArguments() {
- return "value=" + value + ", plain=" + plain + ", style=" + style;
- }
+ public DumperOptions.ScalarStyle getStyle() {
+ return this.style;
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Scalar;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Scalar;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/StreamEndToken.java b/src/main/java/org/yaml/snakeyaml/tokens/StreamEndToken.java
index ece87b95..4998ee32 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/StreamEndToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/StreamEndToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class StreamEndToken extends Token {
- public StreamEndToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public StreamEndToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.StreamEnd;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.StreamEnd;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/StreamStartToken.java b/src/main/java/org/yaml/snakeyaml/tokens/StreamStartToken.java
index 4b5419a6..f085f45c 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/StreamStartToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/StreamStartToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class StreamStartToken extends Token {
- public StreamStartToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public StreamStartToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.StreamStart;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.StreamStart;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/TagToken.java b/src/main/java/org/yaml/snakeyaml/tokens/TagToken.java
index 505a360f..affa00a1 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/TagToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/TagToken.java
@@ -1,41 +1,35 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
import org.yaml.snakeyaml.error.Mark;
public final class TagToken extends Token {
- private final TagTuple value;
- public TagToken(TagTuple value, Mark startMark, Mark endMark) {
- super(startMark, endMark);
- this.value = value;
- }
+ private final TagTuple value;
- public TagTuple getValue() {
- return this.value;
- }
+ public TagToken(TagTuple value, Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ this.value = value;
+ }
- @Override
- protected String getArguments() {
- return "value=[" + value.getHandle() + ", " + value.getSuffix() + "]";
- }
+ public TagTuple getValue() {
+ return this.value;
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Tag;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Tag;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/TagTuple.java b/src/main/java/org/yaml/snakeyaml/tokens/TagTuple.java
index b4ea0646..dd0ef388 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/TagTuple.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/TagTuple.java
@@ -1,37 +1,36 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
public final class TagTuple {
- private final String handle;
- private final String suffix;
- public TagTuple(String handle, String suffix) {
- if (suffix == null) {
- throw new NullPointerException("Suffix must be provided.");
- }
- this.handle = handle;
- this.suffix = suffix;
- }
+ private final String handle;
+ private final String suffix;
- public String getHandle() {
- return handle;
+ public TagTuple(String handle, String suffix) {
+ if (suffix == null) {
+ throw new NullPointerException("Suffix must be provided.");
}
+ this.handle = handle;
+ this.suffix = suffix;
+ }
- public String getSuffix() {
- return suffix;
- }
+ public String getHandle() {
+ return handle;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/Token.java b/src/main/java/org/yaml/snakeyaml/tokens/Token.java
index 8b583f5d..63a49271 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/Token.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/Token.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,64 +17,53 @@ import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
public abstract class Token {
- public enum ID {
- Alias, Anchor, BlockEnd, BlockEntry, BlockMappingStart, BlockSequenceStart, Directive, DocumentEnd, DocumentStart, FlowEntry, FlowMappingEnd, FlowMappingStart, FlowSequenceEnd, FlowSequenceStart, Key, Scalar, StreamEnd, StreamStart, Tag, Value, Whitespace, Comment, Error
- }
- private final Mark startMark;
- private final Mark endMark;
+ public enum ID {
+ Alias("<alias>"), Anchor("<anchor>"), BlockEnd("<block end>"), BlockEntry(
+ "-"), BlockMappingStart("<block mapping start>"), BlockSequenceStart(
+ "<block sequence start>"), Directive("<directive>"), DocumentEnd(
+ "<document end>"), DocumentStart("<document start>"), FlowEntry(
+ ","), FlowMappingEnd("}"), FlowMappingStart("{"), FlowSequenceEnd(
+ "]"), FlowSequenceStart("["), Key("?"), Scalar("<scalar>"), StreamEnd(
+ "<stream end>"), StreamStart("<stream start>"), Tag("<tag>"), Value(
+ ":"), Whitespace("<whitespace>"), Comment("#"), Error("<error>");
- public Token(Mark startMark, Mark endMark) {
- if (startMark == null || endMark == null) {
- throw new YAMLException("Token requires marks.");
- }
- this.startMark = startMark;
- this.endMark = endMark;
- }
+ private final String description;
- public String toString() {
- return "<" + this.getClass().getName() + "(" + getArguments() + ")>";
+ ID(String s) {
+ description = s;
}
- public Mark getStartMark() {
- return startMark;
+ @Override
+ public String toString() {
+ return description;
}
+ }
- public Mark getEndMark() {
- return endMark;
- }
+ private final Mark startMark;
+ private final Mark endMark;
- /**
- * @see "__repr__ for Token in PyYAML"
- */
- protected String getArguments() {
- return "";
+ public Token(Mark startMark, Mark endMark) {
+ if (startMark == null || endMark == null) {
+ throw new YAMLException("Token requires marks.");
}
+ this.startMark = startMark;
+ this.endMark = endMark;
+ }
- /**
- * For error reporting.
- *
- * @see "class variable 'id' in PyYAML"
- */
- public abstract Token.ID getTokenId();
+ public Mark getStartMark() {
+ return startMark;
+ }
- /*
- * for tests only
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Token) {
- return toString().equals(obj.toString());
- } else {
- return false;
- }
- }
+ public Mark getEndMark() {
+ return endMark;
+ }
- /*
- * for tests only
- */
- @Override
- public int hashCode() {
- return toString().hashCode();
- }
+ /**
+ * For error reporting.
+ *
+ * @see "class variable 'id' in PyYAML"
+ * @return ID of this token
+ */
+ public abstract Token.ID getTokenId();
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/ValueToken.java b/src/main/java/org/yaml/snakeyaml/tokens/ValueToken.java
index 58fe0576..504f1c6f 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/ValueToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/ValueToken.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
@@ -19,12 +17,12 @@ import org.yaml.snakeyaml.error.Mark;
public final class ValueToken extends Token {
- public ValueToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
+ public ValueToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
- @Override
- public Token.ID getTokenId() {
- return ID.Value;
- }
+ @Override
+ public Token.ID getTokenId() {
+ return ID.Value;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/tokens/WhitespaceToken.java b/src/main/java/org/yaml/snakeyaml/tokens/WhitespaceToken.java
index 65af212d..f61de83e 100644
--- a/src/main/java/org/yaml/snakeyaml/tokens/WhitespaceToken.java
+++ b/src/main/java/org/yaml/snakeyaml/tokens/WhitespaceToken.java
@@ -1,29 +1,32 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.tokens;
import org.yaml.snakeyaml.error.Mark;
+/**
+ * @deprecated it will be removed because it is not used
+ */
+@Deprecated
public class WhitespaceToken extends Token {
- public WhitespaceToken(Mark startMark, Mark endMark) {
- super(startMark, endMark);
- }
- @Override
- public ID getTokenId() {
- return ID.Whitespace;
- }
+ public WhitespaceToken(Mark startMark, Mark endMark) {
+ super(startMark, endMark);
+ }
+
+ @Override
+ public ID getTokenId() {
+ return ID.Whitespace;
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/util/ArrayStack.java b/src/main/java/org/yaml/snakeyaml/util/ArrayStack.java
index 4bab182a..d0782d15 100644
--- a/src/main/java/org/yaml/snakeyaml/util/ArrayStack.java
+++ b/src/main/java/org/yaml/snakeyaml/util/ArrayStack.java
@@ -1,42 +1,41 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.util;
import java.util.ArrayList;
public class ArrayStack<T> {
- private ArrayList<T> stack;
- public ArrayStack(int initSize) {
- stack = new ArrayList<T>(initSize);
- }
+ private final ArrayList<T> stack;
- public void push(T obj) {
- stack.add(obj);
- }
+ public ArrayStack(int initSize) {
+ stack = new ArrayList<T>(initSize);
+ }
- public T pop() {
- return stack.remove(stack.size() - 1);
- }
+ public void push(T obj) {
+ stack.add(obj);
+ }
- public boolean isEmpty() {
- return stack.isEmpty();
- }
+ public T pop() {
+ return stack.remove(stack.size() - 1);
+ }
- public void clear() {
- stack.clear();
- }
+ public boolean isEmpty() {
+ return stack.isEmpty();
+ }
+
+ public void clear() {
+ stack.clear();
+ }
}
diff --git a/src/main/java/org/yaml/snakeyaml/util/ArrayUtils.java b/src/main/java/org/yaml/snakeyaml/util/ArrayUtils.java
new file mode 100644
index 00000000..9d0aa10d
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/util/ArrayUtils.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.util;
+
+import java.util.AbstractList;
+import java.util.Collections;
+import java.util.List;
+
+public class ArrayUtils {
+
+ private ArrayUtils() {}
+
+ /**
+ * Returns an unmodifiable {@code List} backed by the given array. The method doesn't copy the
+ * array, so the changes to the array will affect the {@code List} as well.
+ *
+ * @param <E> class of the elements in the array
+ * @param elements - array to convert
+ * @return {@code List} backed by the given array
+ */
+ public static <E> List<E> toUnmodifiableList(E[] elements) {
+ return elements.length == 0 ? Collections.<E>emptyList()
+ : new UnmodifiableArrayList<E>(elements);
+ }
+
+ /**
+ * Returns an unmodifiable {@code List} containing the second array appended to the first one. The
+ * method doesn't copy the arrays, so the changes to the arrays will affect the {@code List} as
+ * well.
+ *
+ * @param <E> class of the elements in the array
+ * @param array1 - the array to extend
+ * @param array2 - the array to add to the first
+ * @return {@code List} backed by the given arrays
+ */
+ public static <E> List<E> toUnmodifiableCompositeList(E[] array1, E[] array2) {
+ List<E> result;
+ if (array1.length == 0) {
+ result = toUnmodifiableList(array2);
+ } else if (array2.length == 0) {
+ result = toUnmodifiableList(array1);
+ } else {
+ result = new CompositeUnmodifiableArrayList<E>(array1, array2);
+ }
+ return result;
+ }
+
+ private static class UnmodifiableArrayList<E> extends AbstractList<E> {
+
+ private final E[] array;
+
+ UnmodifiableArrayList(E[] array) {
+ this.array = array;
+ }
+
+ @Override
+ public E get(int index) {
+ if (index >= array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
+ }
+ return array[index];
+ }
+
+ @Override
+ public int size() {
+ return array.length;
+ }
+ }
+
+ private static class CompositeUnmodifiableArrayList<E> extends AbstractList<E> {
+
+ private final E[] array1;
+ private final E[] array2;
+
+ CompositeUnmodifiableArrayList(E[] array1, E[] array2) {
+ this.array1 = array1;
+ this.array2 = array2;
+ }
+
+ @Override
+ public E get(int index) {
+ E element;
+ if (index < array1.length) {
+ element = array1[index];
+ } else if (index - array1.length < array2.length) {
+ element = array2[index - array1.length];
+ } else {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
+ }
+ return element;
+ }
+
+ @Override
+ public int size() {
+ return array1.length + array2.length;
+ }
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/util/EnumUtils.java b/src/main/java/org/yaml/snakeyaml/util/EnumUtils.java
new file mode 100644
index 00000000..da2eb3ed
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/util/EnumUtils.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.util;
+
+public class EnumUtils {
+
+ /**
+ * Looks for an enumeration constant that matches the string without being case sensitive
+ *
+ * @param enumType - the Class object of the enum type from which to return a constant
+ * @param name - the name of the constant to return
+ * @param <T> - the enum type whose constant is to be returned
+ * @return the enum constant of the specified enum type with the specified name, insensitive to
+ * case
+ * @throws IllegalArgumentException – if the specified enum type has no constant with the
+ * specified name, insensitive case
+ */
+ public static <T extends Enum<T>> T findEnumInsensitiveCase(Class<T> enumType, String name) {
+ for (T constant : enumType.getEnumConstants()) {
+ if (constant.name().compareToIgnoreCase(name) == 0) {
+ return constant;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No enum constant " + enumType.getCanonicalName() + "." + name);
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/util/PlatformFeatureDetector.java b/src/main/java/org/yaml/snakeyaml/util/PlatformFeatureDetector.java
new file mode 100644
index 00000000..8d8f0757
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/util/PlatformFeatureDetector.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2008, SnakeYAML
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.yaml.snakeyaml.util;
+
+public class PlatformFeatureDetector {
+
+ private Boolean isRunningOnAndroid = null;
+
+ public boolean isRunningOnAndroid() {
+ if (isRunningOnAndroid == null) {
+ String name = System.getProperty("java.runtime.name");
+ isRunningOnAndroid = (name != null && name.startsWith("Android Runtime"));
+ }
+ return isRunningOnAndroid;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/util/UriEncoder.java b/src/main/java/org/yaml/snakeyaml/util/UriEncoder.java
index e23904ff..02c3e434 100644
--- a/src/main/java/org/yaml/snakeyaml/util/UriEncoder.java
+++ b/src/main/java/org/yaml/snakeyaml/util/UriEncoder.java
@@ -1,17 +1,15 @@
/**
- * Copyright (c) 2008, http://www.snakeyaml.org
+ * Copyright (c) 2008, SnakeYAML
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
*/
package org.yaml.snakeyaml.util;
@@ -20,43 +18,50 @@ import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
-
+import java.nio.charset.StandardCharsets;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.external.com.google.gdata.util.common.base.Escaper;
import org.yaml.snakeyaml.external.com.google.gdata.util.common.base.PercentEscaper;
public abstract class UriEncoder {
- private static final CharsetDecoder UTF8Decoder = Charset.forName("UTF-8").newDecoder()
- .onMalformedInput(CodingErrorAction.REPORT);
- // Include the [] chars to the SAFEPATHCHARS_URLENCODER to avoid
- // its escape as required by spec. See
- // http://yaml.org/spec/1.1/#escaping%20in%20URI/
- private static final String SAFE_CHARS = PercentEscaper.SAFEPATHCHARS_URLENCODER + "[]/";
- private static final Escaper escaper = new PercentEscaper(SAFE_CHARS, false);
- /**
- * Escape special characters with '%'
- */
- public static String encode(String uri) {
- return escaper.escape(uri);
- }
+ private static final CharsetDecoder UTF8Decoder =
+ StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT);
+ // Include the [] chars to the SAFEPATHCHARS_URLENCODER to avoid
+ // its escape as required by spec. See
+ // http://yaml.org/spec/1.1/#escaping%20in%20URI/
+ private static final String SAFE_CHARS = PercentEscaper.SAFEPATHCHARS_URLENCODER + "[]/";
+ private static final Escaper escaper = new PercentEscaper(SAFE_CHARS, false);
- /**
- * Decode '%'-escaped characters. Decoding fails in case of invalid UTF-8
- */
- public static String decode(ByteBuffer buff) throws CharacterCodingException {
- CharBuffer chars = UTF8Decoder.decode(buff);
- return chars.toString();
- }
+ /**
+ * Escape special characters with '%'
+ *
+ * @param uri URI to be escaped
+ * @return encoded URI
+ */
+ public static String encode(String uri) {
+ return escaper.escape(uri);
+ }
+
+ /**
+ * Decode '%'-escaped characters. Decoding fails in case of invalid UTF-8
+ *
+ * @param buff data to decode
+ * @return decoded data
+ * @throws CharacterCodingException if cannot be decoded
+ */
+ public static String decode(ByteBuffer buff) throws CharacterCodingException {
+ CharBuffer chars = UTF8Decoder.decode(buff);
+ return chars.toString();
+ }
- public static String decode(String buff) {
- try {
- return URLDecoder.decode(buff, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new YAMLException(e);
- }
+ public static String decode(String buff) {
+ try {
+ return URLDecoder.decode(buff, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new YAMLException(e);
}
+ }
}