Skip to content

Commit e4eacca

Browse files
committed
feat!: support full JSON number grammar
The initial revision of AQF did not support numbers with exponents prefixed with a plus. This commit allows for that, and so the full number production is supported.
1 parent 0c72820 commit e4eacca

File tree

7 files changed

+323
-40
lines changed

7 files changed

+323
-40
lines changed

module/jsonurl-core/src/main/java/org/jsonurl/stream/AbstractEventIterator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public abstract class AbstractEventIterator
4848
*/
4949
public static final char WFU_VALUE_SEPARATOR = '&';
5050

51+
/**
52+
* x-www-form-urlencoded space (plus).
53+
*/
54+
public static final char WFU_SPACE = '+';
55+
5156
/**
5257
* JsonUrlOptions.
5358
*/

module/jsonurl-core/src/main/java/org/jsonurl/stream/JsonUrlGrammarAQF.java

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,23 @@ class JsonUrlGrammarAQF extends AbstractGrammar {
5353
private static final char ESCAPE = '!';
5454

5555
/**
56-
* Buffer for decoded literal text.
56+
* Buffer for decoded/unescaped literal text.
5757
*/
5858
@SuppressWarnings("PMD.AvoidStringBufferField") // reused
5959
private final StringBuilder decodedTextBuffer = new StringBuilder(512);
6060

61+
/**
62+
* Buffer for non-decoded/escaped literal text.
63+
*/
64+
@SuppressWarnings("PMD.AvoidStringBufferField") // reused
65+
private final StringBuilder numTextBuffer = new StringBuilder(512);
66+
67+
/**
68+
* Reference to either {@link #decodedTextBuffer} or
69+
* {@link #numTextBuffer}.
70+
*/
71+
private CharSequence literalText;
72+
6173
/**
6274
* Construct a new JsonUrlGrammar.
6375
* @param text input text
@@ -77,7 +89,7 @@ public JsonUrlGrammarAQF(
7789
*/
7890
@SuppressWarnings("PMD.CyclomaticComplexity")
7991
private void decodeBang(StringBuilder decodedText, boolean isFirst) {
80-
int cur = nextCodePoint();
92+
int cur = nextCodePoint(false);
8193

8294
switch (cur) {
8395
case '0':
@@ -91,6 +103,7 @@ private void decodeBang(StringBuilder decodedText, boolean isFirst) {
91103
case '8':
92104
case '9':
93105
case '-':
106+
case '+':
94107
case ESCAPE:
95108
case 't':
96109
case 'f':
@@ -103,7 +116,7 @@ private void decodeBang(StringBuilder decodedText, boolean isFirst) {
103116
break;
104117

105118
case 'e':
106-
if (isFirst && decodedText.length() == 0) {
119+
if (isFirst) {
107120
break;
108121
}
109122
// fall through
@@ -118,45 +131,62 @@ protected boolean readAndBufferLiteral() {
118131
final StringBuilder decodedText = this.decodedTextBuffer;
119132
decodedText.setLength(0);
120133

134+
final StringBuilder numText = this.numTextBuffer;
135+
numText.setLength(0);
136+
121137
//
122138
// return true if this has an escape sequence and therefore
123139
// must be parsed as a string value; otherwise, return false.
124140
//
125141
boolean ret = false;
126142

127-
for (;;) {
143+
for (boolean isFirst = true;; isFirst = false) {
144+
final char rawPlus;
145+
128146
//
129147
// The browser address bar *does* recognize a difference between
130-
// percent encoded vs. literal ampersand and equals, unlike other
131-
// sub-delims. So I have to check for those specifically, here,
132-
// because the call to nextCodePoint() will decode them and I can't
133-
// tell the difference at that point.
148+
// percent encoded vs. literal ampersand, equals, and plus, unlike
149+
// other sub-delims. So I have to check for those specifically,
150+
// here, because the call to nextCodePoint() will decode them and
151+
// I can't tell the difference at that point.
134152
//
135153
switch (peekAscii()) { // NOPMD - no default
136154
case EOF:
137155
case WFU_VALUE_SEPARATOR:
138156
case WFU_NAME_SEPARATOR:
139157
return ret;
158+
case WFU_SPACE:
159+
rawPlus = '+';
160+
break;
161+
default:
162+
rawPlus = ' ';
163+
break;
140164
}
141165

142-
int ucp = nextCodePoint();
166+
final int ucp = nextCodePoint();
143167

144168
if (ucp >= CHARBITS_LENGTH) {
145169
decodedText.appendCodePoint(ucp);
170+
numText.appendCodePoint(ucp);
146171
continue;
147172
}
148173

149174
switch (CHARBITS[ucp] & (IS_SPACE
150175
| IS_BANG | IS_LITCHAR | IS_STRUCTCHAR | IS_CGICHAR)) {
151176

152177
case IS_BANG | IS_LITCHAR:
153-
decodeBang(decodedText, !ret);
178+
decodeBang(decodedText, isFirst);
179+
numText.appendCodePoint(ucp);
154180
ret = true;
155181
continue;
156-
case IS_LITCHAR:
157182
case IS_SPACE:
183+
decodedText.appendCodePoint(ucp);
184+
numText.append(rawPlus);
185+
break;
186+
case IS_LITCHAR:
158187
case IS_STRUCTCHAR | IS_CGICHAR:
159188
decodedText.appendCodePoint(ucp);
189+
numText.appendCodePoint(ucp);
160190
continue;
161191
case IS_STRUCTCHAR:
162192
text.pushbackChar(ucp);
@@ -170,9 +200,12 @@ protected boolean readAndBufferLiteral() {
170200

171201
@Override
172202
protected JsonUrlEvent readBufferedLiteral(
173-
boolean wasEscapedString,
203+
boolean isEscaped,
174204
boolean isKey) {
175205

206+
final StringBuilder decodedText = this.decodedTextBuffer;
207+
literalText = decodedText;
208+
176209
if (optionImpliedStringLiterals(options())) {
177210
//
178211
// VALUE_STRING (rather than VALUE_EMPTY_LITERAL) should be
@@ -181,9 +214,7 @@ protected JsonUrlEvent readBufferedLiteral(
181214
return keyEvent(isKey, JsonUrlEvent.VALUE_STRING);
182215
}
183216

184-
final StringBuilder decodedText = this.decodedTextBuffer;
185-
186-
if (wasEscapedString) {
217+
if (isEscaped) {
187218
if (decodedText.length() == 0) {
188219
return keyEvent(isKey, JsonUrlEvent.VALUE_EMPTY_LITERAL);
189220
}
@@ -208,7 +239,10 @@ && optionCoerceNullToEmptyString(options())) {
208239
return keyEvent(isKey, ret);
209240
}
210241

211-
if (numberBuilder.reset().parse(decodedText)) {
242+
final StringBuilder numText = this.numTextBuffer;
243+
244+
if (numberBuilder.reset().parse(numText, options())) {
245+
literalText = numText;
212246
return keyEvent(isKey, JsonUrlEvent.VALUE_NUMBER);
213247
}
214248

@@ -222,7 +256,7 @@ protected JsonUrlEvent readLiteral(boolean isKey) {
222256

223257
@Override
224258
public String getString() {
225-
return decodedTextBuffer.toString();
259+
return literalText.toString();
226260
}
227261

228262
@Override
@@ -236,13 +270,20 @@ protected int peekChar() {
236270
text.pushbackChar(codePoint);
237271
return codePoint;
238272
}
239-
273+
240274
/**
241275
* Read and decode the next codepoint.
242276
*/
243277
private int nextCodePoint() {
278+
return nextCodePoint(true);
279+
}
280+
281+
/**
282+
* Read and decode the next codepoint.
283+
*/
284+
private int nextCodePoint(boolean decodePlus) {
244285
try {
245-
return PercentCodec.decode(text);
286+
return PercentCodec.decode(text, decodePlus);
246287

247288
} catch (IOException e) {
248289
SyntaxException tex = newSyntaxException(MSG_BAD_CHAR);

module/jsonurl-core/src/main/java/org/jsonurl/text/JsonUrlTextAppender.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,11 @@ private static <T extends Appendable> boolean appendLiteral(
444444
}
445445

446446
if (optionImpliedStringLiterals(options)) {
447+
if (optionAQF(options)) {
448+
encodeAqf(dest, text, start, end);
449+
return true;
450+
}
451+
447452
encode(dest, text, start, end, false, true);
448453
return true;
449454
}

module/jsonurl-core/src/main/java/org/jsonurl/text/NumberBuilder.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import static org.jsonurl.BigMathProvider.NEGATIVE_INFINITY;
2121
import static org.jsonurl.BigMathProvider.POSITIVE_INFINITY;
22-
import static org.jsonurl.JsonUrlOption.optionAQF;
2322
import static org.jsonurl.LimitException.Message.MSG_LIMIT_INTEGER;
2423

2524
import java.math.BigDecimal;
@@ -383,8 +382,7 @@ public Exponent getExponentType() {
383382
private static NumberText.Exponent getExponentType(//NOPMD
384383
CharSequence text,
385384
int start,
386-
int stop,
387-
Set<JsonUrlOption> options) {
385+
int stop) {
388386

389387
if (stop <= start) {
390388
return NumberText.Exponent.NONE;
@@ -410,7 +408,7 @@ private static NumberText.Exponent getExponentType(//NOPMD
410408
switch (c) {
411409
case PLUS:
412410
i++;
413-
if (i == stop || optionAQF(options)) {
411+
if (i == stop) {
414412
return NumberText.Exponent.NONE;
415413
}
416414
c = text.charAt(i);
@@ -517,7 +515,7 @@ public boolean parse(//NOPMD
517515
fractIndexStop = pos = digits(text, pos + 1, stop);
518516
}
519517

520-
exponentType = getExponentType(text, pos, stop, options);
518+
exponentType = getExponentType(text, pos, stop);
521519

522520
switch (exponentType) { // NOPMD - SwitchStmtsShouldHaveDefault
523521
case JUST_VALUE:
@@ -663,7 +661,7 @@ public static boolean isNumber(//NOPMD
663661

664662
final int expDigitSkip;
665663

666-
switch (getExponentType(text, pos, stop, options)) {
664+
switch (getExponentType(text, pos, stop)) {
667665
case JUST_VALUE:
668666
expDigitSkip = 1;
669667
pos = digits(text, pos + 1, stop);

0 commit comments

Comments
 (0)