Skip to content

Commit

Permalink
refactor-strict-mode: replace arrayLevels, smallCharMemory in tokener…
Browse files Browse the repository at this point in the history
… with tokener and parserConfig instances in JSONArray
  • Loading branch information
Sean Leary authored and Sean Leary committed May 27, 2024
1 parent 14f7127 commit 0571d73
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 87 deletions.
91 changes: 51 additions & 40 deletions src/main/java/org/json/JSONArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public class JSONArray implements Iterable<Object> {
*/
private final ArrayList<Object> myArrayList;

private JSONTokener jsonTokener;

private JSONParserConfiguration jsonParserConfiguration;

/**
* Construct an empty JSONArray.
*/
Expand All @@ -90,89 +94,77 @@ public JSONArray(JSONTokener x) throws JSONException {
* Constructs a JSONArray from a JSONTokener and a JSONParserConfiguration.
* JSONParserConfiguration contains strictMode turned off (false) by default.
*
* @param x A JSONTokener instance from which the JSONArray is constructed.
* @param jsonTokener A JSONTokener instance from which the JSONArray is constructed.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
* @throws JSONException If a syntax error occurs during the construction of the JSONArray.
*/
public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
public JSONArray(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this();
char nextChar = x.nextClean();
this.jsonTokener = jsonTokener;
this.jsonParserConfiguration = jsonParserConfiguration;

char nextChar = this.jsonTokener.nextClean();

// check first character, if not '[' throw JSONException
if (nextChar != '[') {
throw x.syntaxError("A JSONArray text must start with '['");
throw this.jsonTokener.syntaxError("A JSONArray text must start with '['");
}

parseTokener(x, jsonParserConfiguration); // runs recursively
parseTokener(jsonParserConfiguration); // runs recursively

}

private void parseTokener(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) {
/**
* This method utilizes a JSONTokener initialized with a string to parse a JSONArray
* @param jsonParserConfiguration
*/
private void parseTokener(JSONParserConfiguration jsonParserConfiguration) {
boolean strictMode = jsonParserConfiguration.isStrictMode();

char cursor = x.nextClean();
char cursor = this.jsonTokener.nextClean();

switch (cursor) {
case 0:
throwErrorIfEoF(x);
throwErrorIfEoF();
break;
case ',':
cursor = x.nextClean();
cursor = this.jsonTokener.nextClean();

throwErrorIfEoF(x);
throwErrorIfEoF();

if(strictMode && cursor == ']'){
throw x.syntaxError(getInvalidCharErrorMsg(cursor));
throw this.jsonTokener.syntaxError(getInvalidCharErrorMsg(cursor));
}

if (cursor == ']') {
break;
}

x.back();
this.jsonTokener.back();

parseTokener(x, jsonParserConfiguration);
parseTokener(jsonParserConfiguration);
break;
case ']':
if (strictMode) {
cursor = x.nextClean();
boolean isEoF = x.end();

if (isEoF) {
break;
}

if (x.getArrayLevel() == 0) {
throw x.syntaxError(getInvalidCharErrorMsg(cursor));
}

x.back();
}
break;
default:
x.back();
boolean currentCharIsQuote = x.getPrevious() == '"';
boolean quoteIsNotNextToValidChar = x.getPreviousChar() != ',' && x.getPreviousChar() != '[';

if (strictMode && currentCharIsQuote && quoteIsNotNextToValidChar) {
throw x.syntaxError(getInvalidCharErrorMsg(cursor));
}
this.jsonTokener.back();
boolean currentCharIsQuote = this.jsonTokener.getPrevious() == '"';

this.myArrayList.add(x.nextValue(jsonParserConfiguration));
parseTokener(x, jsonParserConfiguration);
this.myArrayList.add(this.jsonTokener.nextValue(jsonParserConfiguration));
parseTokener(jsonParserConfiguration);
}
}

/**
* Throws JSONException if JSONTokener has reached end of file, usually when array is unclosed. No ']' found,
* instead EoF.
*
* @param x the JSONTokener being evaluated.
* @throws JSONException if JSONTokener has reached end of file.
*/
private void throwErrorIfEoF(JSONTokener x) {
if (x.end()) {
throw x.syntaxError(String.format("Expected a ',' or ']' but instead found '%s'", x.getPrevious()));
private void throwErrorIfEoF() {
if (this.jsonTokener.end()) {
throw this.jsonTokener.syntaxError(String.format("Expected a ',' or ']' but instead found '%s'",
this.jsonTokener.getPrevious()));
}
}

Expand All @@ -188,6 +180,7 @@ private void throwErrorIfEoF(JSONTokener x) {
*/
public JSONArray(String source) throws JSONException {
this(new JSONTokener(source), new JSONParserConfiguration());
confirmStrictModeEof();
}

/**
Expand All @@ -200,6 +193,7 @@ public JSONArray(String source) throws JSONException {
*/
public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source), jsonParserConfiguration);
confirmStrictModeEof();
}

/**
Expand Down Expand Up @@ -318,6 +312,23 @@ public Iterator<Object> iterator() {
return this.myArrayList.iterator();
}

/**
* Convenience method to confirm parsing is completed when in strict mode
* Will throw an exception if there are any chars remaining to
* parse in strict mode. This method should only by called when performing
* a top-level JSONArray parsing (i.e. the JSONArray is the complete JSON doc).
* @throws JSONException if in strict mode and not at end of file
*/
private void confirmStrictModeEof() {
if (this.jsonParserConfiguration.isStrictMode()) {
char c = this.jsonTokener.nextClean();
if (c != 0) {
throw this.jsonTokener.syntaxError(getInvalidCharErrorMsg(c));
}
}

}

/**
* Get the object value associated with an index.
*
Expand Down
46 changes: 0 additions & 46 deletions src/main/java/org/json/JSONTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ public class JSONTokener {
private boolean usePrevious;
/** the number of characters read in the previous line. */
private long characterPreviousLine;
private final List<Character> smallCharMemory;
private int arrayLevel = 0;


/**
* Construct a JSONTokener from a Reader. The caller must close the Reader.
Expand All @@ -53,7 +50,6 @@ public JSONTokener(Reader reader) {
this.character = 1;
this.characterPreviousLine = 0;
this.line = 1;
this.smallCharMemory = new ArrayList<Character>(2);
}


Expand Down Expand Up @@ -191,46 +187,6 @@ public char next() throws JSONException {
return this.previous;
}

private void insertCharacterInCharMemory(Character c) {
boolean foundSameCharRef = checkForEqualCharRefInMicroCharMemory(c);
if(foundSameCharRef){
return;
}

if(smallCharMemory.size() < 2){
smallCharMemory.add(c);
return;
}

smallCharMemory.set(0, smallCharMemory.get(1));
smallCharMemory.remove(1);
smallCharMemory.add(c);
}

private boolean checkForEqualCharRefInMicroCharMemory(Character c) {
boolean isNotEmpty = !smallCharMemory.isEmpty();
if (isNotEmpty) {
Character lastChar = smallCharMemory.get(smallCharMemory.size() - 1);
return c.compareTo(lastChar) == 0;
}

// list is empty so there's no equal characters
return false;
}

/**
* Retrieves the previous char from memory.
*
* @return previous char stored in memory.
*/
public char getPreviousChar() {
return smallCharMemory.get(0);
}

public int getArrayLevel(){
return this.arrayLevel;
}

/**
* Get the last character read from the input or '\0' if nothing has been read yet.
* @return the last character read from the input.
Expand Down Expand Up @@ -317,7 +273,6 @@ public char nextClean() throws JSONException {
for (;;) {
char c = this.next();
if (c == 0 || c > ' ') {
insertCharacterInCharMemory(c);
return c;
}
}
Expand Down Expand Up @@ -474,7 +429,6 @@ public Object nextValue(JSONParserConfiguration jsonParserConfiguration) throws
case '[':
this.back();
try {
this.arrayLevel++;
return new JSONArray(this, jsonParserConfiguration);
} catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e);
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/json/junit/JSONTokenerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void testValid() {
checkValid(" [] ",JSONArray.class);
checkValid("[1,2]",JSONArray.class);
checkValid("\n\n[1,2]\n\n",JSONArray.class);
checkValid("1 2", String.class);
checkValid("\"1 2\"", String.class);
}

@Test
Expand Down

0 comments on commit 0571d73

Please sign in to comment.