Skip to content

Commit dadb47b

Browse files
committed
Start working on #229
1 parent 70686c2 commit dadb47b

File tree

4 files changed

+181
-111
lines changed

4 files changed

+181
-111
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Modules:
1717
(reported by wkwkhautbois@github)
1818
#226: Quote 'y'/'Y'/'n'/'N' as names too (to avoid problems with Boolean keys)
1919
(requested by pnepywoda@github)
20+
- SnakeYAML 1.26 -> 1.27
2021
- Add Gradle Module Metadata (https://blog.gradle.org/alignment-with-gradle-module-metadata)
2122

2223
2.11.3 (02-Oct-2020)

yaml/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<dependency>
3535
<groupId>org.yaml</groupId>
3636
<artifactId>snakeyaml</artifactId>
37-
<version>1.26</version>
37+
<version>1.27</version>
3838
</dependency>
3939

4040
<!-- and for testing need annotations; but should be available via `jackson-databind` above

yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java

+4-110
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
import java.math.BigInteger;
66
import java.util.Arrays;
77
import java.util.Collections;
8-
import java.util.HashSet;
98
import java.util.Map;
10-
import java.util.Set;
119
import java.util.regex.Pattern;
1210

1311
import org.yaml.snakeyaml.DumperOptions;
@@ -20,6 +18,7 @@
2018
import com.fasterxml.jackson.core.base.GeneratorBase;
2119
import com.fasterxml.jackson.core.json.JsonWriteContext;
2220
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
21+
import com.fasterxml.jackson.dataformat.yaml.util.StringQuotingChecker;
2322
import com.fasterxml.jackson.core.io.IOContext;
2423

2524
public class YAMLGenerator extends GeneratorBase
@@ -172,33 +171,6 @@ private Feature(boolean defaultState) {
172171
protected final static Pattern PLAIN_NUMBER_P = Pattern.compile("-?[0-9]*(\\.[0-9]*)?");
173172
protected final static String TAG_BINARY = Tag.BINARY.toString();
174173

175-
/* As per <a href="https://yaml.org/type/bool.html">YAML Spec</a> there are a few
176-
* aliases for booleans, and we better quote such values as keys; although Jackson
177-
* itself has no problems dealing with them, some other tools do have.
178-
*/
179-
// 02-Apr-2019, tatu: Some names will look funny if escaped: let's leave out
180-
// single letter case (esp so 'y' won't get escaped)
181-
// 17-Sep-2020, tatu: [dataformats-text#226] No, let's be consistent w/ values
182-
private final static Set<String> MUST_QUOTE_NAMES = new HashSet<>(Arrays.asList(
183-
"y", "Y", "n", "N",
184-
"yes", "Yes", "YES", "no", "No", "NO",
185-
"true", "True", "TRUE", "false", "False", "FALSE",
186-
"on", "On", "ON", "off", "Off", "OFF"
187-
));
188-
189-
/**
190-
* As per YAML <a href="https://yaml.org/type/null.html">null</a>
191-
* and <a href="https://yaml.org/type/bool.html">boolean</a> type specs,
192-
* better retain quoting for some values
193-
*/
194-
private final static Set<String> MUST_QUOTE_VALUES = new HashSet<>(Arrays.asList(
195-
"y", "Y", "n", "N",
196-
"yes", "Yes", "YES", "no", "No", "NO",
197-
"true", "True", "TRUE", "false", "False", "FALSE",
198-
"on", "On", "ON", "off", "Off", "OFF",
199-
"null", "Null", "NULL"
200-
));
201-
202174
/*
203175
/**********************************************************
204176
/* Configuration
@@ -258,6 +230,7 @@ private Feature(boolean defaultState) {
258230

259231
protected int _rootValueCount;
260232

233+
protected final StringQuotingChecker _quotingChecker = StringQuotingChecker.Default.instance();
261234
/*
262235
/**********************************************************
263236
/* Life-cycle
@@ -462,7 +435,7 @@ public void writeFieldId(long id) throws IOException {
462435
private final void _writeFieldName(String name) throws IOException
463436
{
464437
_writeScalar(name, "string",
465-
_nameNeedsQuoting(name) ? STYLE_QUOTED : STYLE_UNQUOTED_NAME);
438+
_quotingChecker.needToQuoteName(name) ? STYLE_QUOTED : STYLE_UNQUOTED_NAME);
466439
}
467440

468441
/*
@@ -592,7 +565,7 @@ public void writeString(String text) throws IOException,JsonGenerationException
592565
DumperOptions.ScalarStyle style;
593566
if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures)) {
594567
// If one of reserved values ("true", "null"), or, number, preserve quoting:
595-
if (_valueNeedsQuoting(text)
568+
if (_quotingChecker.needToQuoteValue(text)
596569
|| (Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS.enabledIn(_formatFeatures)
597570
&& PLAIN_NUMBER_P.matcher(text).matches())
598571
) {
@@ -944,85 +917,6 @@ private String _base64encode(final Base64Variant b64v, final byte[] input, final
944917
return sb.toString();
945918
}
946919

947-
private boolean _nameNeedsQuoting(String name) {
948-
if (name.length() == 0) { // empty String does indeed require quoting
949-
return true;
950-
}
951-
switch (name.charAt(0)) {
952-
// First, reserved name starting chars:
953-
case 'f': // false
954-
case 'o': // on/off
955-
case 'n': // no
956-
case 't': // true
957-
case 'y': // yes
958-
case 'F': // False
959-
case 'O': // On/Off
960-
case 'N': // No
961-
case 'T': // True
962-
case 'Y': // Yes
963-
return MUST_QUOTE_NAMES.contains(name);
964-
965-
// And then numbers
966-
case '0': case '1': case '2': case '3': case '4':
967-
case '5': case '6': case '7': case '8': case '9':
968-
case '-' : case '+': case '.':
969-
return true;
970-
}
971-
return false;
972-
}
973-
974-
private boolean _valueNeedsQuoting(String name) {
975-
switch (name.charAt(0)) { // caller ensures no empty String
976-
// First, reserved name starting chars:
977-
case 'f': // false
978-
case 'o': // on/off
979-
case 'n': // null/n/no
980-
case 't': // true
981-
case 'y': // y/yes
982-
case 'F': // False/FALSE
983-
case 'O': // On/Off/ON/OFF
984-
case 'N': // Null/NULL/N/No/NO
985-
case 'T': // True/TRUE
986-
case 'Y': // Y/Yes/YES
987-
if (MUST_QUOTE_VALUES.contains(name)) {
988-
return true;
989-
}
990-
break;
991-
}
992-
return _valueHasQuotableChar(name);
993-
}
994-
995-
/**
996-
* As per YAML <a href="https://yaml.org/spec/1.2/spec.html#id2788859">Plain Style</a>unquoted
997-
* strings are restricted to a reduced charset and must be quoted in case they contain
998-
* one of the following characters or character combinations.
999-
*/
1000-
private static boolean _valueHasQuotableChar(String inputStr) {
1001-
for (int i = 0, end = inputStr.length(); i < end; ++i) {
1002-
switch (inputStr.charAt(i)) {
1003-
case '[':
1004-
case ']':
1005-
case '{':
1006-
case '}':
1007-
case ',':
1008-
return true;
1009-
case '\t':
1010-
case ' ':
1011-
if (i < end - 1 && '#' == inputStr.charAt(i + 1)) {
1012-
return true;
1013-
}
1014-
break;
1015-
case ':':
1016-
if (i < end - 1 && (' ' == inputStr.charAt(i + 1) || '\t' == inputStr.charAt(i + 1))) {
1017-
return true;
1018-
}
1019-
break;
1020-
default:
1021-
}
1022-
}
1023-
return false;
1024-
}
1025-
1026920
protected String _lf() {
1027921
return _outputOptions.getLineBreak().getString();
1028922
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package com.fasterxml.jackson.dataformat.yaml.util;
2+
3+
import java.util.Arrays;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
7+
/**
8+
* Helper class that defines API used by
9+
* {@link com.fasterxml.jackson.dataformat.yaml.YAMLGenerator}
10+
* to check whether property names and String values need to be quoted or not.
11+
* Also contains default logic implementation; may be sub-classes to provide
12+
* alternate implementation.
13+
*
14+
* @since 2.12
15+
*/
16+
public abstract class StringQuotingChecker
17+
{
18+
/* As per <a href="https://yaml.org/type/bool.html">YAML Spec</a> there are a few
19+
* aliases for booleans, and we better quote such values as keys; although Jackson
20+
* itself has no problems dealing with them, some other tools do have.
21+
*/
22+
// 02-Apr-2019, tatu: Some names will look funny if escaped: let's leave out
23+
// single letter case (esp so 'y' won't get escaped)
24+
// 17-Sep-2020, tatu: [dataformats-text#226] No, let's be consistent w/ values
25+
private final static Set<String> DEFAULT_QUOTABLE_NAMES = new HashSet<>(Arrays.asList(
26+
"y", "Y", "n", "N",
27+
"yes", "Yes", "YES", "no", "No", "NO",
28+
"true", "True", "TRUE", "false", "False", "FALSE",
29+
"on", "On", "ON", "off", "Off", "OFF",
30+
"null", "Null", "NULL"
31+
));
32+
33+
/**
34+
* As per YAML <a href="https://yaml.org/type/null.html">null</a>
35+
* and <a href="https://yaml.org/type/bool.html">boolean</a> type specs,
36+
* better retain quoting for some values
37+
*/
38+
private final static Set<String> DEFAULT_QUOTABLE_VALUES = new HashSet<>(Arrays.asList(
39+
"false", "False", "FALSE",
40+
"null", "Null", "NULL",
41+
"on", "On", "ON", "off", "Off", "OFF",
42+
"true", "True", "TRUE",
43+
"y", "Y", "n", "N",
44+
"yes", "Yes", "YES", "no", "No", "NO"
45+
));
46+
47+
/**
48+
* Method called by
49+
* {@link com.fasterxml.jackson.dataformat.yaml.YAMLGenerator}
50+
* to check whether given property name should be quoted: usually
51+
* to prevent it from being read as non-String key (boolean or number)
52+
*/
53+
public boolean needToQuoteName(String name)
54+
{
55+
// empty String does indeed require quoting
56+
if (name.length() == 0) {
57+
return true;
58+
}
59+
switch (name.charAt(0)) {
60+
// First, reserved name starting chars:
61+
case 'f': // false
62+
case 'n': // no/n/null
63+
case 'o': // on/off
64+
case 't': // true
65+
case 'y': // yes/y
66+
case 'F': // False
67+
case 'N': // No/N/Null
68+
case 'O': // On/Off
69+
case 'T': // True
70+
case 'Y': // Yes/Y
71+
return isDefaultQuotableName(name);
72+
73+
// And then numbers
74+
case '0': case '1': case '2': case '3': case '4':
75+
case '5': case '6': case '7': case '8': case '9':
76+
case '-' : case '+': case '.':
77+
return true;
78+
}
79+
return false;
80+
}
81+
82+
/**
83+
* Method called by
84+
* {@link com.fasterxml.jackson.dataformat.yaml.YAMLGenerator}
85+
* to check whether given String value should be quoted: usually
86+
* to prevent it from being value of different type (boolean or number)
87+
*/
88+
public boolean needToQuoteValue(String value)
89+
{
90+
// empty String does indeed require quoting
91+
if (value.length() == 0) {
92+
return true;
93+
}
94+
switch (value.charAt(0)) {
95+
// First, reserved name starting chars:
96+
case 'f': // false
97+
case 'n': // no/n/null
98+
case 'o': // on/off
99+
case 't': // true
100+
case 'y': // yes/y
101+
case 'F': // False
102+
case 'N': // No/N/Null
103+
case 'O': // On/Off
104+
case 'T': // True
105+
case 'Y': // Yes/Y
106+
if (isDefaultQuotableValue(value)) {
107+
return true;
108+
}
109+
break;
110+
}
111+
return valueHasQuotableChar(value);
112+
}
113+
114+
protected boolean isDefaultQuotableName(String name) {
115+
return DEFAULT_QUOTABLE_NAMES.contains(name);
116+
}
117+
118+
protected boolean isDefaultQuotableValue(String name) {
119+
return DEFAULT_QUOTABLE_VALUES.contains(name);
120+
}
121+
122+
/**
123+
* As per YAML <a href="https://yaml.org/spec/1.2/spec.html#id2788859">Plain Style</a>unquoted
124+
* strings are restricted to a reduced charset and must be quoted in case they contain
125+
* one of the following characters or character combinations.
126+
*/
127+
protected boolean valueHasQuotableChar(String inputStr)
128+
{
129+
final int end = inputStr.length();
130+
for (int i = 0; i < end; ++i) {
131+
switch (inputStr.charAt(i)) {
132+
case '[':
133+
case ']':
134+
case '{':
135+
case '}':
136+
case ',':
137+
return true;
138+
case '#':
139+
// [dataformats-text#201]: limit quoting with MINIMIZE_QUOTES
140+
if (i > 0) {
141+
char d = inputStr.charAt(i-1);
142+
if (' ' == d || '\t' == d) {
143+
return true;
144+
}
145+
}
146+
break;
147+
case ':':
148+
// [dataformats-text#201]: limit quoting with MINIMIZE_QUOTES
149+
if (i < (end-1)) {
150+
char d = inputStr.charAt(i + 1);
151+
if (' ' == d || '\t' == d) {
152+
return true;
153+
}
154+
}
155+
break;
156+
default:
157+
}
158+
}
159+
return false;
160+
}
161+
162+
/**
163+
* Default {@link StringQuotingChecker} implementation used unless
164+
* custom implementation registered.
165+
*/
166+
public final static class Default
167+
extends StringQuotingChecker
168+
{
169+
private final static Default INSTANCE = new Default();
170+
171+
public Default() { }
172+
173+
public static Default instance() { return INSTANCE; }
174+
}
175+
}

0 commit comments

Comments
 (0)