Skip to content

Commit b6404d1

Browse files
committed
feat: Add support for mv-implied-object and mv-wfu-implied-object
1 parent 174193e commit b6404d1

File tree

5 files changed

+285
-19
lines changed

5 files changed

+285
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2020 David MacCormack
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
* KIND, either express or implied. See the License for the
14+
* specific language governing permissions and limitations
15+
* under the License.
16+
*/
17+
18+
package org.jsonurl;
19+
20+
/**
21+
* A functional interface that provides a value for the given key. This may
22+
* be used with an implied-object or wfu-implied-object that allows missing,
23+
* top-level values.
24+
* @param <V> the return type
25+
*/
26+
@FunctionalInterface
27+
public interface MissingValueProvider<V> {
28+
/**
29+
* Provides a value for the given key.
30+
* This function may also throw a or throws a {@link ParseException} (or derivative)
31+
* if it can not provide a value.
32+
*
33+
* @param key a valid key
34+
* @param pos current parse position
35+
* @return a valid value
36+
*/
37+
V getValue(String key, int pos);
38+
}

module/jsonurl-core/src/main/java/org/jsonurl/ParseResultFacade.java

+11
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,15 @@ R getResult(
135135
*/
136136
boolean isImpliedArray();
137137

138+
/**
139+
* Set a value for this given key.
140+
*
141+
* <p>An implied-object or wfu-implied-object may allow missing,
142+
* top-level values. This method is called to supply it. It may also
143+
* throw a ParseException if the feature is unsupported.
144+
*/
145+
ParseResultFacade<R> addMissingValue(
146+
CharSequence text,
147+
int start,
148+
int stop);
138149
}

module/jsonurl-core/src/main/java/org/jsonurl/Parser.java

+92-7
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,34 @@ S extends V> J parseObject(
328328
JBT impliedObject) {
329329

330330
return (J)parse(text, 0, text.length(), TYPE_VALUE_OBJECT,
331-
new ValueFactoryParseResultFacade<>(factory, null, impliedObject));
331+
new ValueFactoryParseResultFacade<>(
332+
factory, null, impliedObject, null));
333+
}
334+
335+
/**
336+
* Parse a character sequence as a JSON object. This simply calls
337+
* {@link #parse(CharSequence, int, int, ValueType, ValueFactory)
338+
* parse(s, 0, s.length(), EnumSet.of(ValueType.OBJECT), factory)}.
339+
*/
340+
@SuppressWarnings("unchecked") // NOPMD
341+
public <V,
342+
C extends V,
343+
ABT,
344+
A extends C,
345+
JBT,
346+
J extends C,
347+
B extends V,
348+
M extends V,
349+
N extends V,
350+
S extends V> J parseObject(
351+
CharSequence text,
352+
ValueFactory<V,C,ABT,A,JBT,J,B,M,N,S> factory,
353+
JBT impliedObject,
354+
MissingValueProvider<V> mvp) {
355+
356+
return (J)parse(text, 0, text.length(), TYPE_VALUE_OBJECT,
357+
new ValueFactoryParseResultFacade<>(
358+
factory, null, impliedObject, mvp));
332359
}
333360

334361
/**
@@ -354,7 +381,36 @@ S extends V> J parseObject(
354381
JBT impliedObject) {
355382

356383
return (J)parse(text, off, length, TYPE_VALUE_OBJECT,
357-
new ValueFactoryParseResultFacade<>(factory, null, impliedObject));
384+
new ValueFactoryParseResultFacade<>(
385+
factory, null, impliedObject, null));
386+
}
387+
388+
/**
389+
* Parse a character sequence as a JSON object. This simply calls
390+
* {@link #parse(CharSequence, int, int, ValueType, ValueFactory)
391+
* parse(s, off, length, EnumSet.of(ValueType.OBJECT), factory)}.
392+
*/
393+
@SuppressWarnings("unchecked")
394+
public <V,
395+
C extends V,
396+
ABT,
397+
A extends C,
398+
JBT,
399+
J extends C,
400+
B extends V,
401+
M extends V,
402+
N extends V,
403+
S extends V> J parseObject(
404+
CharSequence text,
405+
int off,
406+
int length,
407+
ValueFactory<V,C,ABT,A,JBT,J,B,M,N,S> factory,
408+
JBT impliedObject,
409+
MissingValueProvider<V> mvp) {
410+
411+
return (J)parse(text, off, length, TYPE_VALUE_OBJECT,
412+
new ValueFactoryParseResultFacade<>(
413+
factory, null, impliedObject, mvp));
358414
}
359415

360416
/**
@@ -423,7 +479,8 @@ S extends V> A parseArray(
423479
ABT impliedArray) {
424480

425481
return (A)parse(text, 0, text.length(), TYPE_VALUE_ARRAY,
426-
new ValueFactoryParseResultFacade<>(factory, impliedArray, null));
482+
new ValueFactoryParseResultFacade<>(
483+
factory, impliedArray, null, null));
427484
}
428485

429486
/**
@@ -449,7 +506,8 @@ S extends V> A parseArray(
449506
ABT impliedArray) {
450507

451508
return (A)parse(text, off, length, TYPE_VALUE_ARRAY,
452-
new ValueFactoryParseResultFacade<>(factory, impliedArray, null));
509+
new ValueFactoryParseResultFacade<>(
510+
factory, impliedArray, null, null));
453511
}
454512

455513
/**
@@ -537,7 +595,7 @@ S extends V> V parse(
537595
ValueFactory<V,C,ABT,A,JBT,J,B,M,N,S> factory) {
538596
return parse(text, 0, text.length(), EnumSet.of(canReturn), factory);
539597
}
540-
598+
541599
/**
542600
* Parse the given JSON&#x2192;URL text and return a JSON value.
543601
* The parse will start at the character offset given by {@code off}, and
@@ -572,7 +630,7 @@ S extends V> V parse(
572630
ValueFactory<V,C,ABT,A,JBT,J,B,M,N,S> factory) {
573631

574632
return parse(text, off, length, canReturn,
575-
new ValueFactoryParseResultFacade<>(factory, null, null));
633+
new ValueFactoryParseResultFacade<>(factory, null, null, null));
576634
}
577635

578636
/**
@@ -747,7 +805,7 @@ private <R> R parse(
747805
pos++;
748806
continue;
749807

750-
case ')':
808+
case END_COMPOSITE:
751809
//
752810
// found open paren followed by close paren; the empty
753811
// composite value.
@@ -1141,6 +1199,14 @@ private <R> R parse(
11411199
pos += litlen;
11421200

11431201
if (pos == stop) {
1202+
if (impliedObject && parseDepth == 1) {
1203+
return result
1204+
.setLocation(litpos)
1205+
.addMissingValue(text, litpos, pos)
1206+
.addObjectElement()
1207+
.endObject()
1208+
.getResult();
1209+
}
11441210
throw new SyntaxException(MSG_STILL_OPEN, pos);
11451211
}
11461212

@@ -1155,6 +1221,25 @@ private <R> R parse(
11551221
case NAME_SEPARATOR:
11561222
break;
11571223

1224+
case WFU_VALUE_SEPARATOR:
1225+
if (!wwwFormUrlEncoded || parseDepth != 1) {
1226+
throw new SyntaxException(MSG_EXPECT_OBJECT_VALUE, pos);
1227+
}
1228+
// fall through
1229+
case VALUE_SEPARATOR:
1230+
if (impliedObject && parseDepth == 1) {
1231+
//
1232+
// this may be a key that's missing a value; give
1233+
// the result a chance to handle that case.
1234+
//
1235+
result
1236+
.setLocation(litpos)
1237+
.addMissingValue(text, litpos, pos);
1238+
1239+
stateStack.set(0, State.OBJECT_AFTER_ELEMENT);
1240+
continue;
1241+
}
1242+
// fall through
11581243
default:
11591244
throw new SyntaxException(MSG_EXPECT_OBJECT_VALUE, pos);
11601245
}

module/jsonurl-core/src/main/java/org/jsonurl/ValueFactoryParseResultFacade.java

+54-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.jsonurl.JsonUrl.Parse.literal;
2121
import static org.jsonurl.JsonUrl.Parse.literalToJavaString;
2222
import static org.jsonurl.JsonUrl.Parse.newNumberBuilder;
23+
import static org.jsonurl.SyntaxException.Message.MSG_EXPECT_OBJECT_VALUE;
2324

2425
import java.util.Deque;
2526
import java.util.LinkedList;
@@ -55,6 +56,11 @@ class ValueFactoryParseResultFacade<V,
5556
*/
5657
private final Deque<Object> builderStack = new LinkedList<>();
5758

59+
/**
60+
* The current parse position.
61+
*/
62+
private int position;
63+
5864
/**
5965
* reusable buffer.
6066
*
@@ -84,17 +90,25 @@ class ValueFactoryParseResultFacade<V,
8490
*/
8591
private boolean impliedObject;
8692

93+
/**
94+
* A MissingValueProvider.
95+
*/
96+
private final MissingValueProvider<V> missingValueProvider;
97+
8798
/**
8899
* Create a new ValueFactoryParseResultFacade.
89100
*/
90101
public ValueFactoryParseResultFacade(
91102
ValueFactory<V,C,ABT,A,JBT,J,B,M,N,S> factory,
92103
ABT impliedArray,
93-
JBT impliedObject) {
104+
JBT impliedObject,
105+
MissingValueProvider<V> missingValueProvider) {
94106

95107
this.factory = factory;
96108
this.numb = newNumberBuilder(factory);
97-
109+
this.missingValueProvider = missingValueProvider == null
110+
? this::defaultMissingValueProvier : missingValueProvider;
111+
98112
if (impliedArray != null) {
99113
this.impliedArray = true;
100114
builderStack.push(impliedArray);
@@ -175,8 +189,7 @@ public void addObjectKey(
175189
int start,
176190
int stop,
177191
boolean isEmptyUnquotedStringOK) {
178-
keyStack.push(literalToJavaString(
179-
buf, numb, text, start, stop, isEmptyUnquotedStringOK));
192+
keyStack.push(parseKey(text, start, stop, isEmptyUnquotedStringOK));
180193
}
181194

182195
@Override
@@ -228,10 +241,7 @@ public boolean isValid(Set<ValueType> canReturn, V result) {
228241

229242
@Override
230243
public ParseResultFacade<V> setLocation(int location) {
231-
//
232-
// this class doesn't throw any exceptions so I don't need to
233-
// store the location
234-
//
244+
this.position = location;
235245
return this;
236246
}
237247

@@ -244,4 +254,40 @@ public boolean isImpliedArray() {
244254
public boolean isImpliedObject() {
245255
return impliedObject;
246256
}
257+
258+
@Override
259+
public ParseResultFacade<V> addMissingValue(
260+
CharSequence text,
261+
int start,
262+
int stop) {
263+
264+
final String key = parseKey(text, start, stop, false);
265+
keyStack.push(key);
266+
factoryValueStack.push(missingValueProvider.getValue(key, position));
267+
268+
return this;
269+
}
270+
271+
/**
272+
* Parse the object key.
273+
*/
274+
private String parseKey(
275+
CharSequence text,
276+
int start,
277+
int stop,
278+
boolean isEmptyUnquotedStringOK) {
279+
280+
return literalToJavaString(
281+
buf, numb, text, start, stop, isEmptyUnquotedStringOK);
282+
}
283+
284+
/**
285+
* default implementation of MissingValueProvider.
286+
*/
287+
private V defaultMissingValueProvier(String key, int pos) {
288+
throw new SyntaxException(
289+
MSG_EXPECT_OBJECT_VALUE,
290+
String.format("%s: %s", MSG_EXPECT_OBJECT_VALUE, key),
291+
pos);
292+
}
247293
}

0 commit comments

Comments
 (0)