Skip to content
Rick Hightower edited this page Jul 18, 2023 · 2 revisions

io.nats.jparse.token

class diagram

Token

The Token class represents a token that has been parsed from a JSON string. It includes the start and end indices of the token within the source, as well as its type. The class provides methods for getting the string representation of the token, as well as its length and a string representation that includes its start and end indices and type. Additionally, the class includes equals and hashCode methods for comparing tokens.

io.nats.jparse.parser

class diagram

JsonParserBuilder

JsonParserBuilder

Builder class for creating instances of JsonParser.

Please refer to JsonParserBuilder#build() for details on how the build logic is formed.

public JsonParser build()

public JsonParser build() {
    if (strict()) {
        return new JsonStrictParser(objectsKeysCanBeEncoded());
    } else if (isSupportNoQuoteKeys() || isAllowHashComment() || isAllowSlashSlashComment() || isAllowSlashStarComment() || parseKey != null) {
        final ParseFunction[] funcTable = this.getFuncTable();
        funcTable[ParseConstants.STRING_START_TOKEN] = JsonParserFunctions::parseString;
        funcTable[ParseConstants.NULL_START] = JsonParserFunctions::parseNull;
        funcTable[ParseConstants.TRUE_BOOLEAN_START] = JsonParserFunctions::parseTrue;
        funcTable[ParseConstants.FALSE_BOOLEAN_START] = JsonParserFunctions::parseFalse;
        for (int i = ParseConstants.NUM_0; i < ParseConstants.NUM_9 + 1; i++) {
            funcTable[i] = JsonParserFunctions::parseNumber;
        }
        funcTable[ParseConstants.MINUS] = JsonParserFunctions::parseNumber;
        funcTable[ParseConstants.PLUS] = JsonParserFunctions::parseNumber;
        if (isAllowHashComment()) {
            funcTable['#'] = (source, tokens) -> source.findChar('\n');
        }
        if (isSupportNoQuoteKeys() && getParseKey() == null) {
            setParseKey(JsonParserFunctions::parseKeyNoQuote);
        }
        if (isAllowSlashStarComment() || isAllowSlashSlashComment()) {
            funcTable['/'] = (source, tokens) -> {
                int next = source.next();
                if (next == '/') {
                    source.findChar('\n');
                } else if (next == '*') {
                    loop: while (source.findChar('*')) {
                        switch(source.next()) {
                            case '/':
                                break loop;
                            case ParseConstants.ETX:
                                throw new UnexpectedCharacterException("Comment parse", "End of stream", source);
                        }
                    }
                }
            };
        }
        return new JsonFuncParser(objectsKeysCanBeEncoded(), Arrays.copyOf(funcTable, funcTable.length), this.getDefaultFunc(), this.getParseKey());
    } else {
        return new JsonFastParser(objectsKeysCanBeEncoded());
    }
}

The build() method in the JsonParserBuilder class is responsible for creating and returning an instance of JsonParser based on the configuration set in the builder. Here is a step-by-step description of what the method is doing:

  1. If the strict() flag is set to true, a new JsonStrictParser instance is created with the objectsKeysCanBeEncoded() flag and returned.
  2. If the isSupportNoQuoteKeys(), isAllowHashComment(), isAllowSlashSlashComment(), or isAllowSlashStarComment() flags are set to true, or if the parseKey is not null, additional configuration is applied to the JsonParser.
  3. An array of ParseFunction called funcTable is retrieved using the getFuncTable() method.
  4. The funcTable is populated with different parse functions for each token or character:
    • The STRING_START_TOKEN is mapped to JsonParserFunctions::parseString.
    • The NULL_START is mapped to JsonParserFunctions::parseNull.
    • The TRUE_BOOLEAN_START is mapped to JsonParserFunctions::parseTrue.
    • The FALSE_BOOLEAN_START is mapped to JsonParserFunctions::parseFalse.
    • The numbers from NUM_0 to NUM_9 are mapped to JsonParserFunctions::parseNumber.
    • The MINUS is mapped to JsonParserFunctions::parseNumber.
    • The PLUS is mapped to JsonParserFunctions::parseNumber.
    • If the isAllowHashComment() flag is true, the # character is mapped to a lambda function that finds the next character on a new line.
    • If the isSupportNoQuoteKeys() flag is true and the parseKey is null, the parseKeyNoQuote function is set as the parseKey in the builder.
    • If the isAllowSlashStarComment() or isAllowSlashSlashComment() flags are true, the / character is mapped to a lambda function that handles comments. If the next character is /, it finds the next character on a new line. If the next character is *, it processes the comment until it finds */.
  5. Finally, if none of the above conditions are met, a new JsonFastParser instance is created with the objectsKeysCanBeEncoded() flag and returned.

The returned JsonParser instance has different behavior based on the configuration options set in the builder.

sequence diagram

public JsonEventParser buildEventParser()

public JsonEventParser buildEventParser() {
    if (strict()) {
        return new JsonEventStrictParser(objectsKeysCanBeEncoded(), tokenEventListener());
    } else {
        return new JsonEventFastParser(objectsKeysCanBeEncoded(), tokenEventListener());
    }
}

The buildEventParser method in the JsonParserBuilder class is designed to create and return an instance of the JsonEventParser interface. Here is a step-by-step description of what the method does based on its body:

  1. Check if the strict() method returns true.

    • If true, proceed to step 2.
    • If false, proceed to step 3.
  2. Create a new instance of the JsonEventStrictParser class.

    • Pass the result of the objectsKeysCanBeEncoded() method as the first argument.
    • Pass the result of the tokenEventListener() method as the second argument.
    • Return the created JsonEventStrictParser instance.
  3. Create a new instance of the JsonEventFastParser class.

    • Pass the result of the objectsKeysCanBeEncoded() method as the first argument.
    • Pass the result of the tokenEventListener() method as the second argument.
    • Return the created JsonEventFastParser instance.

Note that the choice between using JsonEventStrictParser and JsonEventFastParser depends on the return value of the strict() method. If strict() is true, the strict parser is used; otherwise, the fast parser is used.

sequence diagram

io.nats.jparse.parser.functable

class diagram

JsonParserFunctions

The JsonParserFunctions class is a collection of functions that are utilized by the JsonFuncParser to parse JSON.

public static boolean parseKeyNoQuote(final CharSource source, final TokenList tokens)

public static boolean parseKeyNoQuote(final CharSource source, final TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex() - 1;
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean found = false;
    switch(ch) {
        case ParseConstants.STRING_START_TOKEN:
            final int strStartIndex = startIndex + 1;
            final int strEndIndex = source.findEndOfEncodedString();
            tokens.add(new Token(strStartIndex + 1, strEndIndex, TokenTypes.STRING_TOKEN));
            found = true;
            break;
        case ParseConstants.OBJECT_END_TOKEN:
            tokens.undoPlaceholder();
            return true;
        default:
            if (Character.isAlphabetic(ch)) {
                final int start = source.getIndex();
                final int end = source.findAttributeEnd();
                tokens.add(new Token(start, end, TokenTypes.STRING_TOKEN));
                found = true;
            } else {
                throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
            }
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        tokens.set(tokenListIndex, new Token(startIndex + 1, source.getIndex(), TokenTypes.ATTRIBUTE_KEY_TOKEN));
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

The method parseKeyNoQuote in the JsonParserFunctions class takes two parameters: source of type CharSource and tokens of type TokenList. It returns a boolean value.

Here is a step-by-step description of what the method does based on its body:

  1. It retrieves the next character from the source and skips any leading white spaces.
  2. It saves the current index of the source minus 1 as startIndex.
  3. It saves the current index of the tokens as tokenListIndex.
  4. It adds a placeholder token to the tokens list.
  5. It initializes a boolean variable found as false.
  6. It enters a switch statement based on the value of the character ch.
    • If ch is equal to ParseConstants.STRING_START_TOKEN:
      • It calculates the start index of the string as strStartIndex by adding 1 to startIndex.
      • It calculates the end index of the string as strEndIndex by calling the findEndOfEncodedString method on the source object.
      • It adds a new Token object to the tokens list with the start index, end index, and type TokenTypes.STRING_TOKEN.
      • It sets found to true.
    • If ch is equal to ParseConstants.OBJECT_END_TOKEN:
      • It undoes the placeholder token added to the tokens list.
      • It returns true.
    • If ch is neither ParseConstants.STRING_START_TOKEN nor ParseConstants.OBJECT_END_TOKEN:
      • If ch is an alphabetic character:
        • It saves the current index of the source as start.
        • It finds the end index of the attribute by calling the findAttributeEnd method on the source object and saves it as end.
        • It adds a new Token object to the tokens list with the start index, end index, and type TokenTypes.STRING_TOKEN.
        • It sets found to true.
      • If ch is not an alphabetic character:
        • It throws an UnexpectedCharacterException with the message "Unexpected character found" and the source object as arguments.
  7. It checks if it needs to find the end of the object or the attribute separator by calling the findObjectEndOrAttributeSep method on the source object. The result is saved in the done variable.
  8. If done is false and found is true:
    • It updates the placeholder token in the tokens list with the start index as startIndex + 1 and the current index of the source as the end index, and the type TokenTypes.ATTRIBUTE_KEY_TOKEN.
  9. If found is true and done is true:
    • It throws an UnexpectedCharacterException with the message "Not found" and the source object as arguments.
  10. It returns the value of done. sequence diagram

public static boolean parseKeyWithEncode(final CharSource source, final TokenList tokens)

public static boolean parseKeyWithEncode(final CharSource source, final TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex() - 1;
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean found = false;
    switch(ch) {
        case ParseConstants.STRING_START_TOKEN:
            final int strStartIndex = startIndex + 1;
            final int strEndIndex = source.findEndOfEncodedString();
            tokens.add(new Token(strStartIndex + 1, strEndIndex, TokenTypes.STRING_TOKEN));
            found = true;
            break;
        case ParseConstants.OBJECT_END_TOKEN:
            tokens.undoPlaceholder();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        tokens.set(tokenListIndex, new Token(startIndex + 1, source.getIndex(), TokenTypes.ATTRIBUTE_KEY_TOKEN));
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

parseKeyWithEncode Method Description

The parseKeyWithEncode method is defined in the io.nats.jparse.parser.functable.JsonParserFunctions class. It takes two parameters: source of type CharSource and tokens of type TokenList. The method has a return type of boolean.

The purpose of this method is to parse a key while encoding it, given a character source (source) and a list of tokens (tokens).

Below is a step-by-step description of what the method does:

  1. The method starts by retrieving the next character from the source, skipping any white space characters.

  2. It initializes two variables: startIndex to hold the index of the character in the source minus 1 and tokenListIndex to hold the current index of the tokens list.

  3. A placeholder token is added to the tokens list.

  4. The method enters a switch statement based on the value of the retrieved character.

  5. If the retrieved character is equal to the constant ParseConstants.STRING_START_TOKEN, the method proceeds to parse a string. It initializes two variables: strStartIndex to hold the index of the start of the string plus 1 and strEndIndex to hold the index of the end of the encoded string. A new Token object is created with the start and end indexes, and a token type of TokenTypes.STRING_TOKEN. The newly created token is added to the tokens list. The found variable is set to true.

  6. If the retrieved character is equal to the constant ParseConstants.OBJECT_END_TOKEN, the method undoes the placeholder token and returns true.

  7. If none of the above cases match, an UnexpectedCharacterException is thrown, indicating that an unexpected character was found while parsing the key.

  8. The method then tries to find the end of the object or the attribute separator. The done variable is set to true if the end is found.

  9. If done is false and found is true, the method updates the token at the tokenListIndex in the tokens list with a new Token object that has the updated start and end indexes, and a token type of TokenTypes.ATTRIBUTE_KEY_TOKEN.

  10. If found is true and done is true, an UnexpectedCharacterException is thrown, indicating that the key was not found.

  11. Finally, the method returns the value of the done variable.

This method is used to parse a key while encoding it, and it handles different cases based on the character read from the source. If the key is successfully parsed, the method updates the tokens list accordingly. However, if an unexpected character is encountered or the key is not found, an exception is thrown. sequence diagram

public static boolean parseKeyNoEncode(final CharSource source, final TokenList tokens)

public static boolean parseKeyNoEncode(final CharSource source, final TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex() - 1;
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean found = false;
    switch(ch) {
        case ParseConstants.STRING_START_TOKEN:
            final int strStartIndex = startIndex + 1;
            final int strEndIndex = source.findEndString();
            tokens.add(new Token(strStartIndex + 1, strEndIndex, TokenTypes.STRING_TOKEN));
            found = true;
            break;
        case ParseConstants.OBJECT_END_TOKEN:
            tokens.undoPlaceholder();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        tokens.set(tokenListIndex, new Token(startIndex + 1, source.getIndex(), TokenTypes.ATTRIBUTE_KEY_TOKEN));
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

sequence diagram

JsonFuncParser

The JsonFuncParser class is an implementation of the JsonParser interface. It uses a function table to define the behavior of the parser. The function table, which is an array of ParseFunction objects, determines which function to call when parsing a specific character. The function table can be customized by subclassing this class and overriding the initFuncTable method. Additionally, the function table can be configured using the JsonParserBuilder.

private void doParse(final CharSource source, final TokenList tokens, final int ch)

private void doParse(final CharSource source, final TokenList tokens, final int ch) {
    if (ch < 256) {
        funcTable[ch].parse(source, tokens);
    } else {
        defaultFunc.parse(source, tokens);
    }
}

The method doParse defined in class io.nats.jparse.parser.functable.JsonFuncParser is a private method with the following step-by-step description:

  1. Accepts three parameters: source of type CharSource, tokens of type TokenList, and ch of type int.
  2. Checks if the value of ch is less than 256.
  3. If ch is less than 256: a. Retrieves the appropriate parser from the funcTable array at index ch. b. Invokes the parse method on the retrieved parser, passing source and tokens as arguments.
  4. If ch is not less than 256: a. Invokes the parse method on the defaultFunc, passing source and tokens as arguments.

This method essentially determines the appropriate parser to use based on the value of ch and delegates the parsing task to the selected parser using the parse method. sequence diagram

private void parseArray(final CharSource source, final TokenList tokens)

private void parseArray(final CharSource source, final TokenList tokens) {
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseArrayItem(source, tokens);
    }
    final Token arrayToken = new Token(startSourceIndex, source.getIndex(), TokenTypes.ARRAY_TOKEN);
    tokens.set(tokenListIndex, arrayToken);
}

The method parseArray is defined in the class io.nats.jparse.parser.functable.JsonFuncParser. It takes two parameters: source of type CharSource and tokens of type TokenList.

Here is a step-by-step description of what the method does:

  1. It stores the current index of the source in a variable called startSourceIndex.
  2. It also stores the current index of the tokens in a variable called tokenListIndex.
  3. It inserts a placeholder token in the tokens list using the placeHolder() method.
  4. It initializes a boolean variable called done to false.
  5. It enters a while loop that continues until done becomes true.
  6. Inside the while loop, it calls the parseArrayItem() method passing source and tokens as parameters. The parseArrayItem() method is responsible for parsing each item in the array.
  7. After parseArrayItem() returns, it checks the value of done and updates it accordingly.
  8. Once all the items in the array have been parsed, it creates a new Token object called arrayToken using the startSourceIndex, current index of the source, and the type TokenTypes.ARRAY_TOKEN.
  9. Finally, it updates the token at tokenListIndex in the tokens list with the newly created arrayToken.

That's the step-by-step description of what the parseArray method does based on its body. sequence diagram

private boolean parseArrayItem(CharSource source, TokenList tokens)

private boolean parseArrayItem(CharSource source, TokenList tokens) {
    char ch = (char) source.nextSkipWhiteSpace();
    forLoop: for (; ch != ETX; ch = (char) source.nextSkipWhiteSpace()) {
        switch(ch) {
            case ARRAY_END_TOKEN:
                source.next();
                return true;
            case ARRAY_SEP:
                source.next();
                return false;
            default:
                doParse(source, tokens, ch);
                break forLoop;
        }
    }
    if (source.getCurrentChar() == ARRAY_END_TOKEN) {
        source.next();
        return true;
    }
    return false;
}

The parseArrayItem method is defined in the JsonFuncParser class, which is located in the package io.nats.jparse.parser.functable.

This method takes two parameters:

  • source of type CharSource, which represents the source of characters to parse.
  • tokens of type TokenList, which represents the list of tokens that have been parsed so far.

Here is a step-by-step description of what the parseArrayItem method does based on its body:

  1. Retrieve the next character from the source and assign it to the variable ch. Skip any whitespace characters.
  2. Start a for loop that iterates until the character ETX (end of text) is encountered.
  3. Within the for loop, check the value of ch using a switch statement.
  4. If ch is equal to ARRAY_END_TOKEN (representing the end of the array), move the source to the next character and return true to indicate that the array item has been successfully parsed.
  5. If ch is equal to ARRAY_SEP (representing the separator between array items), move the source to the next character and return false to indicate that more array items need to be parsed.
  6. If none of the above cases match (i.e., ch is any other character), call the doParse method to further parse the item using the current character ch. Then, break out of the for loop to stop parsing the current array item and move on to the next one.
  7. After the for loop ends, check if the current character in the source is equal to ARRAY_END_TOKEN.
  8. If it is, move the source to the next character and return true to indicate that the array item has been successfully parsed.
  9. If the current character in the source is not equal to ARRAY_END_TOKEN, return false to indicate that the array item has not been fully parsed yet.

That's the step-by-step description of what the parseArrayItem method does based on its body. sequence diagram

private boolean parseValue(final CharSource source, TokenList tokens)

private boolean parseValue(final CharSource source, TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    doParse(source, tokens, ch);
    ch = source.skipWhiteSpace();
    switch(ch) {
        case OBJECT_END_TOKEN:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return true;
        case OBJECT_ATTRIBUTE_SEP:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return false;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, source.getCurrentChar());
    }
}

The parseValue method in the JsonFuncParser class is used to parse a JSON value from a given source and add it to a list of tokens. Here is a step-by-step description of the method's functionality:

  1. Get the next character from the source, skipping any white spaces.
  2. Store the current index of the source as the starting index of the value.
  3. Store the current index of the tokens list.
  4. Create a placeholder token in the tokens list to represent the parsed value. This placeholder will be replaced later with the actual value token.
  5. Call the doParse method to parse the value and update the tokens list.
  6. Get the next character from the source after parsing the value, skipping any white spaces.
  7. Check the value of the next character using a switch statement.
  8. If the next character is an object end token, check that the source index is equal to the token list index. If it is not, throw an UnexpectedCharacterException indicating that there was a key separator before the value. Otherwise, update the placeholder token in the token list with the correct start and end indexes and set its type as ATTRIBUTE_VALUE_TOKEN. Finally, return true to indicate that the parsing is successful.
  9. If the next character is an object attribute separator, perform the same check as in step 8. If the check fails, throw an UnexpectedCharacterException indicating that there was a key separator before the value. Otherwise, update the placeholder token in the token list with the correct start and end indexes and set its type as ATTRIBUTE_VALUE_TOKEN. Finally, return false to indicate that the parsing is not yet complete.
  10. If none of the above cases match, throw an UnexpectedCharacterException indicating that there is an unexpected character in the value, along with the current character from the source.

This method is responsible for parsing a JSON value from a source and adding it to the tokens list, while also handling different scenarios such as object end token and object attribute separator. sequence diagram

private void parseObject(final CharSource source, TokenList tokens)

private void parseObject(final CharSource source, TokenList tokens) {
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseKey.parse(source, tokens);
        if (!done)
            done = parseValue(source, tokens);
    }
    source.next();
    tokens.set(tokenListIndex, new Token(startSourceIndex, source.getIndex(), TokenTypes.OBJECT_TOKEN));
}

The parseObject() method in the class io.nats.jparse.parser.functable.JsonFuncParser is responsible for parsing a JSON object. Here is a step-by-step description of what this method is doing based on its body:

  1. The method begins by creating two local variables: startSourceIndex and tokenListIndex. These variables are assigned the current index of the source and tokens.

  2. The tokens.placeHolder() method is called to create a placeholder token in the tokens list.

  3. A boolean variable named done is declared and initialized as false. This variable is used to indicate whether the parsing of the object is complete.

  4. A while loop is started with the condition !done. This loop continues until the parsing of the object is complete.

  5. The parseKey.parse(source, tokens) method is called to parse a key from the source. If the parsing is successful and returns true, the value of done is set to true, indicating that the parsing of the object is complete. If the parsing of the key is not successful, the method parseValue(source, tokens) is called to parse a value.

  6. Once the parsing of the object is complete, the source.next() method is called to move the index of the source to the next character.

  7. The tokens.set(tokenListIndex, new Token(startSourceIndex, source.getIndex(), TokenTypes.OBJECT_TOKEN)) method is called to set the token at tokenListIndex to a new Token object representing the parsed JSON object. The startSourceIndex is the starting index of the object, and the current index of the source is the ending index of the object. The TokenTypes.OBJECT_TOKEN indicates that the token represents an object.

In summary, the parseObject() method parses a JSON object by repeatedly calling methods to parse keys and values until the entire object is parsed. It then updates the tokens list with a new token representing the parsed object. sequence diagram

io.nats.jparse.parser.event

class diagram

JsonEventFastParser

The JsonEventFastParser class is a public class that extends the JsonEventAbstractParser. It is a fast JSON event parser.

@Override

public void parseWithEvents(CharSource source, final TokenEventListener event)

@Override
public void parseWithEvents(CharSource source, final TokenEventListener event) {
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, event);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, event);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, event);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, event);
            break;
        case NULL_START:
            parseNull(source, event);
            break;
        case STRING_START_TOKEN:
            parseString(source, event);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, event);
            break;
        default:
            throw new UnexpectedCharacterException("Scanning JSON", "Unexpected character", source, (char) ch);
    }
}

The parseWithEvents method in the JsonEventFastParser class is used to parse JSON content with event callbacks. Here is a step-by-step description of what this method does:

  1. It takes two parameters: source, which is a CharSource object representing the JSON content to parse, and event, which is a TokenEventListener object that will receive the parsing events.

  2. The method calls source.nextSkipWhiteSpace() to read the next character from the source and skips any leading white spaces. The returned value is stored in the ch variable.

  3. It enters a switch statement based on the value of ch.

  4. If ch equals OBJECT_START_TOKEN, the method calls the parseObject method, passing the source and event parameters. This method is responsible for parsing JSON objects.

  5. If ch equals ARRAY_START_TOKEN, the method calls the parseArray method, passing the source and event parameters. This method is responsible for parsing JSON arrays.

  6. If ch equals TRUE_BOOLEAN_START, the method calls the parseTrue method, passing the source and event parameters. This method is responsible for parsing the JSON "true" boolean value.

  7. If ch equals FALSE_BOOLEAN_START, the method calls the parseFalse method, passing the source and event parameters. This method is responsible for parsing the JSON "false" boolean value.

  8. If ch equals NULL_START, the method calls the parseNull method, passing the source and event parameters. This method is responsible for parsing the JSON "null" value.

  9. If ch equals STRING_START_TOKEN, the method calls the parseString method, passing the source and event parameters. This method is responsible for parsing JSON strings.

  10. If ch equals any of the number-related characters (NUM_0, NUM_1, NUM_2, etc.), the method calls the parseNumber method, passing the source and event parameters. This method is responsible for parsing JSON numeric values.

  11. If none of the above cases match, it means that an unexpected character was encountered, and the method throws an UnexpectedCharacterException, passing a message, the current source, and the unexpected character.

Overall, the parseWithEvents method determines the type of JSON value based on the first character of the input source, and then delegates the parsing to specific methods accordingly. It ensures that the appropriate parsing method is called based on the type of JSON token encountered, and if an unexpected character is encountered, it throws an exception.

The method parseWithEvents in the JsonEventFastParser class is used to parse a JSON input using an event-based approach. It takes a CharSource object representing the input source and a TokenEventListener object to handle the parsing events.

Within the method, it reads the first character from the input source and determines the type of token it represents. Based on the token, the method dispatches the parsing task to corresponding methods such as parseObject, parseArray, parseTrue, parseFalse, parseNull, parseString, or parseNumber.

If the first character does not match any of the expected JSON tokens, it throws an exception indicating an unexpected character.

Overall, this method enables parsing of JSON input using event-based parsing technique, where the parser notifies the listener about different events encountered during parsing, such as the start and end of JSON objects or arrays, the discovery of boolean values, null values, strings, or numeric values.

sequence diagram

private void parseArray(final CharSource source, final TokenEventListener event)

private void parseArray(final CharSource source, final TokenEventListener event) {
    event.start(TokenTypes.ARRAY_TOKEN, source.getIndex(), source);
    boolean done = false;
    while (!done) {
        done = parseArrayItem(source, event);
        if (!done) {
            done = source.findCommaOrEndForArray();
        }
    }
    event.end(TokenTypes.ARRAY_TOKEN, source.getIndex(), source);
}

The parseArray method in the JsonEventFastParser class is responsible for parsing JSON arrays. Here is a step-by-step breakdown of what this method does:

  1. The method takes two arguments: source, which is a CharSource object representing the source of the JSON data, and event, which is a TokenEventListener object used for notifying events during parsing.

  2. The method starts by calling the start method of the event object to notify the start of an array token, providing the token type, the index of the source, and the source itself.

  3. A boolean variable done is initialized to false, indicating the parsing process is not yet completed.

  4. The method enters a while loop that continues until the parsing is complete (indicated by done being set to true).

  5. Inside the loop, the method calls the parseArrayItem method to parse each item in the array. This method is responsible for handling the inner elements of the array and returns a boolean value indicating whether the parsing for the item is complete or not.

  6. If the parsing for the current item is not complete (indicated by the returned value of parseArrayItem being false), the method calls the findCommaOrEndForArray method of the source object. This method is responsible for finding the next comma separator or the end of the array in the source and returns a boolean value indicating whether the parsing for the array is complete or not.

  7. The while loop continues or terminates based on the value of done. If done is true, the parsing is complete, and the loop will exit.

  8. Finally, the method calls the end method of the event object to notify the end of the array token, providing the token type, the index of the source, and the source itself.

This method essentially iterates over the elements of a JSON array, calling the appropriate parsing methods and notifying the events to the TokenEventListener during the process.

The parseArray method in the JsonEventFastParser class is used to parse an array from a given input source and notify the provided TokenEventListener of the encountered array tokens.

The method starts by calling the start method of the TokenEventListener to notify the start of the array token.

Then, it enters a loop to parse each item in the array. It calls the parseArrayItem method to parse each item, and if the parsing is not yet done, it checks if there is a comma (,) or end (]) of the array.

Once the parsing is done, it calls the end method of the TokenEventListener to notify the end of the array token.

sequence diagram

private boolean parseArrayItem(CharSource source, final TokenEventListener event)

private boolean parseArrayItem(CharSource source, final TokenEventListener event) {
    char startChar = source.getCurrentChar();
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case OBJECT_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseObject(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ARRAY_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseArray(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case TRUE_BOOLEAN_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseTrue(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case FALSE_BOOLEAN_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseFalse(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case NULL_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseNull(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case STRING_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseString(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseNumber(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            if (source.getCurrentChar() == ARRAY_END_TOKEN || source.getCurrentChar() == ARRAY_SEP) {
                if (source.getCurrentChar() == ARRAY_END_TOKEN) {
                    source.next();
                    return true;
                }
            }
            break;
        case ARRAY_END_TOKEN:
            if (startChar == ARRAY_SEP) {
                throw new UnexpectedCharacterException("Parsing Array Item", "Trailing comma", source, (char) ch);
            }
            source.next();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing Array Item", "Unexpected character", source, (char) ch);
    }
    return false;
}

The parseArrayItem method is responsible for parsing an item within a JSON array. Here is a step-by-step description of what this method does based on its body:

  1. The method takes two parameters: a CharSource object, which represents the source of characters to be parsed, and a TokenEventListener object, which handles the parsing events.

  2. The method starts by getting the current character from the CharSource and storing it in the startChar variable. It then calls the nextSkipWhiteSpace method on the CharSource to get the next non-whitespace character and stores it in the ch variable.

  3. The method performs a switch statement on the value of ch to determine the type of JSON value to be parsed:

    • If ch is equal to OBJECT_START_TOKEN, it means that the item is an object. The method calls the start method on the TokenEventListener to indicate the start of an array item, followed by invoking the parseObject method to parse the nested object. After parsing the object, the end method is called on the TokenEventListener to indicate the end of the array item.

    • If ch is equal to ARRAY_START_TOKEN, it means that the item is an array. The method follows the same steps as described for the object case, but calls the parseArray method instead to parse the nested array.

    • If ch is equal to TRUE_BOOLEAN_START, it means that the item is a boolean value true. The method follows the same steps as described for the object case, but calls the parseTrue method instead to parse the value true.

    • If ch is equal to FALSE_BOOLEAN_START, it means that the item is a boolean value false. The method follows the same steps as described for the object case, but calls the parseFalse method instead to parse the value false.

    • If ch is equal to NULL_START, it means that the item is a null value. The method follows the same steps as described for the object case, but calls the parseNull method instead to parse the value null.

    • If ch is equal to STRING_START_TOKEN, it means that the item is a string value. The method follows the same steps as described for the object case, but calls the parseString method instead to parse the string value.

    • If ch is one of the characters NUM_0 to NUM_9, MINUS, or PLUS, it means that the item is a numeric value. The method follows the same steps as described for the object case, but calls the parseNumber method instead to parse the numeric value. After parsing the number, it checks if the current character in the source is either the ARRAY_END_TOKEN or the ARRAY_SEP. If it's the ARRAY_END_TOKEN, it means that the array ends after this item, so the method returns true to indicate that the parsing of the array is complete. If it's the ARRAY_SEP, it means that more items are present in the array, so the method continues parsing the next item by returning false.

    • If ch is equal to ARRAY_END_TOKEN, it means that the array ends after this item. The method checks if the startChar is the ARRAY_SEP character, which indicates a trailing comma in the array. If it's a trailing comma, the method throws an UnexpectedCharacterException with a message indicating the presence of a trailing comma. Otherwise, it simply moves to the next character in the source and returns true to indicate that the parsing of the array is complete.

    • If none of the above cases match, it means that the character ch is not a valid character for an item in the JSON array. The method throws an UnexpectedCharacterException with a message indicating that an unexpected character was encountered while parsing the array item.

  4. Finally, if none of the above cases match and no exception is thrown, the method returns false to indicate that the parsing of the array is not yet complete and there are more items to be parsed.

This is a general overview of the parseArrayItem method and its behavior based on the provided code snippet.

The parseArrayItem method in the JsonEventFastParser class is responsible for parsing an item within a JSON array.

It takes in a CharSource object, which represents the source of characters to be parsed, and a TokenEventListener object, which handles the events generated during parsing.

The method starts by getting the current character from the CharSource and then reads the next character after skipping any white space.

It then checks the value of the character and performs different actions based on its value. If the character indicates the start of an object, it calls the parseObject method and generates the appropriate start and end tokens for the array item. Similarly, if the character indicates the start of an array, boolean value (true/false), null value, string, or number, it calls the respective parsing methods and generates the corresponding tokens.

If the character is a digit or special character that indicates the start of a number, the method parses the number and checks if the current character is either the end of the array or a comma separator. If it is the end of the array, the method returns true to indicate that the item has been parsed successfully.

If the character is the end token of an array, the method returns true if and only if the start character of the array item is a comma separator. This is to check if there is a trailing comma after the last item in the array, which is not allowed in JSON.

If none of the above conditions are met, the method throws an exception indicating that an unexpected character was encountered.

Finally, the method returns false to indicate that the item has not been fully parsed yet.

This method is used internally by the parser to parse individual items within a JSON array.

private void parseNumber(final CharSource source, final TokenEventListener event)

private void parseNumber(final CharSource source, final TokenEventListener event) {
    final int startIndex = source.getIndex();
    final NumberParseResult numberParse = source.findEndOfNumber();
    final int tokenType = numberParse.wasFloat() ? TokenTypes.FLOAT_TOKEN : TokenTypes.INT_TOKEN;
    event.start(tokenType, startIndex, source);
    event.end(tokenType, numberParse.endIndex(), source);
}

The parseNumber method, which is defined in the class io.nats.jparse.parser.event.JsonEventFastParser, is responsible for parsing numbers from a given character source and notifying a token event listener about the parsed number.

Here is a step-by-step description of what the parseNumber method does:

  1. It takes two parameters: source, which is the character source containing the JSON string, and event, which is the token event listener that will be notified about the parsed number.

  2. It retrieves the current index of the character source using the getIndex() method and assigns it to the startIndex variable. This will be the starting index of the parsed number.

  3. It calls the findEndOfNumber() method on the source object to find the end index of the number. This method scans the character source and determines the end index of the number, taking into account the decimal point and scientific notation if present. The result of this operation is stored in the numberParse variable, which is of type NumberParseResult.

  4. It determines the token type based on the result of the wasFloat() method called on numberParse. If the parsed number is a float, the token type is set to TokenTypes.FLOAT_TOKEN. Otherwise, it is set to TokenTypes.INT_TOKEN.

  5. It notifies the token event listener that a number token is starting by calling the start() method on event. This method takes three parameters: the token type, the startIndex, and the source object. This allows the event listener to handle the start of a number token.

  6. It notifies the token event listener that a number token has ended by calling the end() method on event. This method also takes three parameters: the token type, the end index obtained from numberParse, and the source object. This allows the event listener to handle the end of a number token.

Overall, the parseNumber method scans the character source to find the end index of a number, determines its type (float or integer), and notifies the token event listener about the start and end of the number token.

The parseNumber method in the JsonEventFastParser class is responsible for parsing a number from a given CharSource and notifying the TokenEventListener about the parsed number.

Here is a breakdown of what the method does:

  1. It saves the starting index of the current parsing position in the source using source.getIndex().
  2. Then, it uses the findEndOfNumber() method of the source to parse the number and stores the result in numberParse.
  3. Next, it determines the tokenType based on whether the parsed number was a float or an integer, using the wasFloat() method of numberParse.
  4. It notifies the TokenEventListener that a new token is starting by calling its start() method with the tokenType, the startIndex, and the source.
  5. Finally, it notifies the TokenEventListener that the token has ended by calling its end() method with the tokenType, the endIndex() obtained from numberParse, and the source.

In summary, the parseNumber method parses a number from the source and lets the TokenEventListener know about the token's type, starting and ending positions.

sequence diagram

private boolean parseKey(final CharSource source, final TokenEventListener event)

private boolean parseKey(final CharSource source, final TokenEventListener event) {
    int ch = source.nextSkipWhiteSpace();
    event.start(TokenTypes.ATTRIBUTE_KEY_TOKEN, source.getIndex(), source);
    boolean found = false;
    switch(ch) {
        case STRING_START_TOKEN:
            final int strStartIndex = source.getIndex();
            event.start(TokenTypes.STRING_TOKEN, strStartIndex + 1, source);
            final int strEndIndex;
            if (objectsKeysCanBeEncoded) {
                strEndIndex = source.findEndOfEncodedString();
            } else {
                strEndIndex = source.findEndString();
            }
            found = true;
            event.end(TokenTypes.STRING_TOKEN, strEndIndex, source);
            break;
        case OBJECT_END_TOKEN:
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        event.end(TokenTypes.ATTRIBUTE_KEY_TOKEN, source.getIndex(), source);
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

The parseKey method is defined in the JsonEventFastParser class of the io.nats.jparse.parser.event package.

Here is a step-by-step description of what the parseKey method does based on its body:

  1. It takes two parameters: source of type CharSource and event of type TokenEventListener.
  2. It retrieves the next character from the source while skipping any whitespace.
  3. It notifies the event that a new ATTRIBUTE_KEY_TOKEN is being started, providing the index and the source.
  4. It uses a switch statement to handle different cases for the retrieved character:
    • If the character is STRING_START_TOKEN, it means that a new string token is starting.
      • It notifies the event that a new STRING_TOKEN is being started, providing the index plus one (to exclude the STRING_START_TOKEN) and the source.
      • It determines the end index of the string token:
        • If objectsKeysCanBeEncoded is true, it uses the findEndOfEncodedString() method of the source to locate the end of the encoded string.
        • If objectsKeysCanBeEncoded is false, it uses the findEndString() method of the source to locate the end of the string.
      • It sets found to true to indicate that a string token has been found.
      • It notifies the event that the STRING_TOKEN is ending, providing the end index and the source.
    • If the character is OBJECT_END_TOKEN, it means that the end of the object is reached.
      • It returns true to indicate that the parsing of the key is done.
    • If none of the above cases match, it throws an UnexpectedCharacterException with a message indicating that an unexpected character is found in the key parsing, along with the source.
  5. It attempts to find the end of the object or the attribute separator in the source, and assigns the result to the done variable.
  6. If done is false and found is true, it notifies the event that the ATTRIBUTE_KEY_TOKEN is ending, providing the current index and the source.
  7. If found is true and done is true, it throws an UnexpectedCharacterException with a message indicating that the end of the key is not found, along with the source.
  8. It returns the value of done, indicating whether the parsing of the key is done or not.

The method parseKey is a part of the JsonEventFastParser class in the io.nats.jparse.parser.event package.

This method is responsible for parsing the key of a JSON attribute. It takes a CharSource object and a TokenEventListener object as parameters.

The method begins by skipping any whitespace characters in the source and then starts the attribute key token event. It then checks the next character in the source.

If the next character is a string start token, the method starts a string token event, determines the end index of the string, and sets the found flag to true. The method then ends the string token event.

If the next character is an object end token, the method returns true.

If none of the above conditions are met, the method throws an UnexpectedCharacterException with a relevant message.

Finally, the method checks if the parsing is done by finding the end of the object or the attribute separator in the source. If parsing is not yet done and the found flag is true, the method ends the attribute key token event. If parsing is done and the found flag is true, the method throws an UnexpectedCharacterException with a relevant message.

The method returns the value of the done variable, indicating whether the parsing of the key is completed or not.

private boolean parseValue(final CharSource source, final TokenEventListener event)

private boolean parseValue(final CharSource source, final TokenEventListener event) {
    int ch = source.nextSkipWhiteSpace();
    event.start(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, event);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, event);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, event);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, event);
            break;
        case NULL_START:
            parseNull(source, event);
            break;
        case STRING_START_TOKEN:
            parseString(source, event);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, event);
            break;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, ch);
    }
    source.skipWhiteSpace();
    switch(source.getCurrentChar()) {
        case OBJECT_END_TOKEN:
            event.end(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
            return true;
        case OBJECT_ATTRIBUTE_SEP:
            event.end(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
            return false;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, source.getCurrentChar());
    }
}

The parseValue method in the JsonEventFastParser class is responsible for parsing a JSON value from a given source and generating corresponding events through a TokenEventListener.

Here is a step-by-step description of what the parseValue method does based on its body:

  1. The method starts by skipping any white space characters in the source and then notifies the event listener that a new attribute value token is about to be processed.

  2. It reads the next character from the source and performs a switch case based on its value:

    • If the character is the start of an object ({), it calls the parseObject method to parse the object.
    • If the character is the start of an array ([), it calls the parseArray method to parse the array.
    • If the character is the start of a boolean true value (true), it calls the parseTrue method to parse the true value.
    • If the character is the start of a boolean false value (false), it calls the parseFalse method to parse the false value.
    • If the character is the start of a null value (null), it calls the parseNull method to parse the null value.
    • If the character is the start of a string ("), it calls the parseString method to parse the string.
    • If the character is a digit or a minus/plus sign (0-9, -, +), it calls the parseNumber method to parse a number.
    • If none of the above cases match, it throws an UnexpectedCharacterException with an error message.
  3. After parsing the value, the method skips any remaining white space characters in the source.

  4. It then performs another switch case based on the current character in the source:

    • If the character is the end of an object (}), it notifies the event listener that the attribute value token has ended and returns true.
    • If the character is the separator between object attributes (:), it notifies the event listener that the attribute value token has ended and returns false.
    • If none of the above cases match, it throws an UnexpectedCharacterException with an error message.

This method is responsible for handling the main types of JSON values (objects, arrays, booleans, null, strings, and numbers) and generating appropriate events for each value type through the event listener.

The parseValue method in the JsonEventFastParser class is responsible for parsing a JSON value from a given CharSource and notifying a TokenEventListener with the parsed value.

First, it reads the next character from the source and skips any whitespace. It then notifies the event listener that it is starting to parse an attribute value token.

Based on the type of the character read, it performs the following actions:

  • If the character is the start of an object token, it invokes the parseObject method.
  • If the character is the start of an array token, it invokes the parseArray method.
  • If the character is the start of a true boolean token, it invokes the parseTrue method.
  • If the character is the start of a false boolean token, it invokes the parseFalse method.
  • If the character is the start of a null token, it invokes the parseNull method.
  • If the character is the start of a string token, it invokes the parseString method.
  • If the character is a digit or a symbol related to numbers, it invokes the parseNumber method.
  • If none of the above cases match, it throws an exception for encountering an unexpected character.

After parsing the value and skipping any whitespace, it checks the current character of the source. If it is the end of an object token, it notifies the event listener and returns true. If it is a comma separating object attributes, it notifies the event listener and returns false. If none of these cases match, it throws an exception for encountering an unexpected character.

Overall, the parseValue method efficiently parses and validates a JSON value, handling various types such as objects, arrays, booleans, null, numbers, and strings.

sequence diagram

private void parseObject(final CharSource source, final TokenEventListener event)

private void parseObject(final CharSource source, final TokenEventListener event) {
    event.start(TokenTypes.OBJECT_TOKEN, source.getIndex(), source);
    boolean done = false;
    while (!done) {
        done = parseKey(source, event);
        if (!done)
            done = parseValue(source, event);
    }
    if (source.getCurrentChar() != OBJECT_END_TOKEN) {
        throw new UnexpectedCharacterException("Parsing Object", "Unexpected character", source, source.getCurrentCharSafe());
    }
    source.next();
    event.end(TokenTypes.OBJECT_TOKEN, source.getIndex(), source);
}

The parseObject method in the JsonEventFastParser class is used to parse a JSON object. Here's a step-by-step description of how the method works:

  1. It starts by invoking the start method of the TokenEventListener with the token type OBJECT_TOKEN to indicate the beginning of parsing an object. It also provides the index and the character source.

  2. It initializes a boolean variable done to false, which will be used to determine when parsing is complete.

  3. It enters a while loop, which continues until done is true. This loop iteratively parses the key-value pairs within the JSON object.

  4. Inside the loop, it calls the parseKey method to parse the key of the key-value pair. The parseKey method is expected to return a boolean value indicating whether the key parsing is complete.

  5. If the parseKey method returns false, it means the key parsing is not yet complete, so it calls the parseValue method to parse the value of the key-value pair.

  6. After parsing the key and value, it checks if the current character is the end of the object token (OBJECT_END_TOKEN). If it's not, it throws an UnexpectedCharacterException, indicating that an unexpected character was found when parsing the JSON object.

  7. If the current character is the end of the object token, it moves to the next character in the source by invoking the next method.

  8. Finally, it invokes the end method of the TokenEventListener with the token type OBJECT_TOKEN to indicate the end of parsing an object. It also provides the index and the character source.

This method essentially handles the parsing of a JSON object by calling helper methods to parse the key-value pairs and verifying the end of the object token.

The parseObject method in the class io.nats.jparse.parser.event.JsonEventFastParser is responsible for parsing a JSON object from a given input source.

The method starts by notifying the event listener that an object token is being parsed. It then enters a loop, where it alternates between parsing a key and parsing a value until the end of the object is reached.

After the loop, the method checks if the last character of the input source is the expected object end token. If it's not, an UnexpectedCharacterException is thrown.

Finally, the method notifies the event listener that the parsing of the object is complete.

sequence diagram

JsonEventAbstractParser

The JsonEventAbstractParser class provides shared code for event parsers. It is an abstract class that implements the JsonEventParser and JsonParser interfaces. This class serves as a base for implementing event-based JSON parsers.

@Override

public List scan(final CharSource source)

@Override
public List<Token> scan(final CharSource source) {
    tokenList = new TokenList();
    this.parseWithEvents(source, base);
    return tokenList;
}

Method: scan()

This method is defined in the io.nats.jparse.parser.event.JsonEventAbstractParser class.

Parameters:

  • source: The CharSource object representing the input source to be scanned.

Return Type:

  • List<Token>: The list of tokens generated by the scan process.

Steps:

  1. Create a new TokenList object to store the generated tokens.
  2. Call the parseWithEvents method passing the source and base parameters, along with the current instance of the class. This method is responsible for parsing the input source and generating events based on the JSON syntax.
  3. Return the populated tokenList which now contains the tokens generated during the parsing process.

The scan method is an overridden method in the JsonEventAbstractParser class. It accepts a CharSource object as a parameter and returns a list of Token objects.

Within the method, a new instance of TokenList is created. The parseWithEvents method is called with the provided source and base parameters. This method is responsible for parsing the source and generating appropriate events based on the JSON structure.

Once the parsing is completed, the tokenList, which contains the generated Token objects, is returned. The Token objects represent the different elements found in the JSON structure, such as keys, values, arrays, and objects.

sequence diagram

JsonEventStrictParser

The JsonEventStrictParser class is a strict JSON parser that extends the JsonEventAbstractParser class. It provides functionality for parsing JSON data in a strict manner.

@Override

public void parseWithEvents(CharSource source, final TokenEventListener event)

@Override
public void parseWithEvents(CharSource source, final TokenEventListener event) {
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case ParseConstants.OBJECT_START_TOKEN:
            parseObject(source, event);
            break;
        case ParseConstants.ARRAY_START_TOKEN:
            parseArray(source, event);
            break;
        case ParseConstants.TRUE_BOOLEAN_START:
            parseTrue(source, event);
            break;
        case ParseConstants.FALSE_BOOLEAN_START:
            parseFalse(source, event);
            break;
        case ParseConstants.NULL_START:
            parseNull(source, event);
            break;
        case ParseConstants.STRING_START_TOKEN:
            parseString(source, event);
            break;
        case ParseConstants.NUM_0:
        case ParseConstants.NUM_1:
        case ParseConstants.NUM_2:
        case ParseConstants.NUM_3:
        case ParseConstants.NUM_4:
        case ParseConstants.NUM_5:
        case ParseConstants.NUM_6:
        case ParseConstants.NUM_7:
        case ParseConstants.NUM_8:
        case ParseConstants.NUM_9:
        case ParseConstants.MINUS:
        case ParseConstants.PLUS:
            parseNumber(source, event);
            break;
        default:
            throw new UnexpectedCharacterException("Scanning JSON", "Unexpected character", source, (char) ch);
    }
    source.checkForJunk();
}

The parseWithEvents method in the JsonEventStrictParser class is responsible for parsing a JSON document step by step and triggering events based on the encountered tokens.

Here is a step-by-step description of what this method does:

  1. It takes two parameters: source, which is the CharSource (a character source providing the JSON document), and event, which is the TokenEventListener (to handle events).

  2. It reads the next character from the source and skips any leading whitespace.

  3. It uses a switch statement to determine the type of token based on the read character:

    • If the token is an object start token ({), it calls the parseObject method, passing the source and event as parameters.

    • If the token is an array start token ([), it calls the parseArray method, passing the source and event as parameters.

    • If the token is a true boolean start token, it calls the parseTrue method, passing the source and event as parameters.

    • If the token is a false boolean start token, it calls the parseFalse method, passing the source and event as parameters.

    • If the token is a null start token, it calls the parseNull method, passing the source and event as parameters.

    • If the token is a string start token ("), it calls the parseString method, passing the source and event as parameters.

    • If the token is a numeric digit or a minus/plus sign, it calls the parseNumber method, passing the source and event as parameters.

    • If the token does not match any of the above cases, it throws an UnexpectedCharacterException with a descriptive error message.

  4. After parsing the token, it checks for any additional junk characters in the source and throws an exception if any leftovers are found.

In summary, the parseWithEvents method reads each character from the input JSON document, identifies its type, and calls the appropriate parsing method. It also ensures that there are no unexpected characters or junk at the end of the document.

The parseWithEvents method in the JsonEventStrictParser class is used to parse a JSON input using events.

Here's what the method does:

  1. It reads the next character from the input source and skips any whitespace.
  2. It then checks the type of the character and performs the appropriate action:
    • If the character represents the start of an object ('{'), it calls the parseObject method.
    • If the character represents the start of an array ('['), it calls the parseArray method.
    • If the character represents the start of a true boolean value ('t'), it calls the parseTrue method.
    • If the character represents the start of a false boolean value ('f'), it calls the parseFalse method.
    • If the character represents the start of a null value ('n'), it calls the parseNull method.
    • If the character represents the start of a string ('"'), it calls the parseString method.
    • If the character represents a number (0-9, '-', '+'), or a decimal point, it calls the parseNumber method.
    • If none of the above cases match, it throws an UnexpectedCharacterException.
  3. After parsing the value, it checks for any additional junk characters in the input source using the checkForJunk method.

Overall, the parseWithEvents method allows for parsing a JSON input by handling different JSON types and invoking appropriate event callbacks for each type.

sequence diagram

private void parseArray(final CharSource source, final TokenEventListener event)

private void parseArray(final CharSource source, final TokenEventListener event) {
    levelCheck(source);
    event.start(TokenTypes.ARRAY_TOKEN, source.getIndex(), source);
    boolean done = false;
    while (!done) {
        done = parseArrayItem(source, event);
        if (!done) {
            done = source.findCommaOrEndForArray();
        }
    }
    event.end(TokenTypes.ARRAY_TOKEN, source.getIndex(), source);
}

The parseArray method is a private method defined in the io.nats.jparse.parser.event.JsonEventStrictParser class. It takes two parameters: source of type CharSource and event of type TokenEventListener.

Here is a step-by-step description of what the parseArray method does based on its body:

  1. It starts by calling a method called levelCheck passing the source parameter. This method performs some checks related to the level of the parsing.
  2. It then calls the start method on the event parameter, passing TokenTypes.ARRAY_TOKEN, source.getIndex() (current index in the source), and source itself. This indicates the start of an array token to the TokenEventListener.
  3. It initializes a boolean variable called done as false.
  4. It enters a while loop that continues until done is true.
  5. Inside the loop, it calls a method called parseArrayItem passing the source and event parameters. This method is responsible for parsing each item within the array.
  6. After the parseArrayItem method returns, it checks if the done variable is still false.
    • If done is still false, it calls the findCommaOrEndForArray method on the source to find the next comma or end of the array.
    • If done is true, it exits the while loop.
  7. After the while loop, it calls the end method on the event parameter, passing TokenTypes.ARRAY_TOKEN, source.getIndex() (current index in the source), and source itself. This indicates the end of the array token to the TokenEventListener.

In summary, the parseArray method is responsible for parsing an array from the source using the parseArrayItem method and notifying the TokenEventListener about the start and end of the array token.

The parseArray method is a private method defined in the JsonEventStrictParser class. It is responsible for parsing an array from a given CharSource while notifying an TokenEventListener about the encountered events.

Here is a breakdown of what the method does:

  1. It performs a level check on the provided CharSource.
  2. It notifies the TokenEventListener that the start of an array token has been encountered.
  3. A loop is initiated until the parsing of the array is complete.
  4. Inside the loop, it calls the parseArrayItem method to parse each item in the array. The TokenEventListener is notified about the parsed item.
  5. After parsing an item, it checks if the parsing is complete by calling the findCommaOrEndForArray method on the CharSource. If a comma is found, the loop continues to parse the next item. If the end of the array is encountered, the loop is terminated.
  6. Finally, it notifies the TokenEventListener that the end of the array token has been encountered, providing the index of the CharSource.

In summary, the parseArray method is responsible for parsing an array from a CharSource and notifying the TokenEventListener about the start and end of the array token, as well as each individual array item.

sequence diagram

private boolean parseArrayItem(CharSource source, final TokenEventListener event)

private boolean parseArrayItem(CharSource source, final TokenEventListener event) {
    char startChar = source.getCurrentChar();
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case ParseConstants.OBJECT_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseObject(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.ARRAY_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseArray(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.TRUE_BOOLEAN_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseTrue(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.FALSE_BOOLEAN_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseFalse(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.NULL_START:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseNull(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.STRING_START_TOKEN:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseString(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            break;
        case ParseConstants.NUM_0:
        case ParseConstants.NUM_1:
        case ParseConstants.NUM_2:
        case ParseConstants.NUM_3:
        case ParseConstants.NUM_4:
        case ParseConstants.NUM_5:
        case ParseConstants.NUM_6:
        case ParseConstants.NUM_7:
        case ParseConstants.NUM_8:
        case ParseConstants.NUM_9:
        case ParseConstants.MINUS:
        case ParseConstants.PLUS:
            event.start(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            parseNumber(source, event);
            event.end(TokenTypes.ARRAY_ITEM_TOKEN, source.getIndex(), source);
            if (source.getCurrentChar() == ParseConstants.ARRAY_END_TOKEN || source.getCurrentChar() == ParseConstants.ARRAY_SEP) {
                if (source.getCurrentChar() == ParseConstants.ARRAY_END_TOKEN) {
                    source.next();
                    return true;
                }
            }
            break;
        case ParseConstants.ARRAY_END_TOKEN:
            if (startChar == ParseConstants.ARRAY_SEP) {
                throw new UnexpectedCharacterException("Parsing Array Item", "Trailing comma", source, (char) ch);
            }
            source.next();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing Array Item", "Unexpected character", source, (char) ch);
    }
    return false;
}

The parseArrayItem method in the JsonEventStrictParser class is responsible for parsing individual items in a JSON array. Here is a step-by-step description of what the method does:

  1. The method takes two parameters: source, which is the input source containing the JSON array, and event, which is the event listener that will be notified of parsing events.

  2. It starts by getting the current character from the source and storing it in the startChar variable.

  3. The method then calls the nextSkipWhitespace method on the input source to skip any whitespace characters and get the next character in the source, which is stored in the ch variable.

  4. The method enters a switch statement based on the value of ch.

  5. If the value of ch is equal to ParseConstants.OBJECT_START_TOKEN (which represents the start of a JSON object), it notifies the event listener that an array item has started, and then calls the parseObject method to parse the JSON object. After the object is parsed, it notifies the event listener that the array item has ended.

  6. If the value of ch is equal to ParseConstants.ARRAY_START_TOKEN (which represents the start of another JSON array), it follows a similar process as in step 5, but calls the parseArray method instead to parse the array.

  7. If the value of ch is equal to ParseConstants.TRUE_BOOLEAN_START or ParseConstants.FALSE_BOOLEAN_START (which represent the start of a boolean value), it again follows a similar process as in step 5, but calls the parseTrue or parseFalse method respectively to parse the boolean value.

  8. If the value of ch is equal to ParseConstants.NULL_START (which represents the start of a null value), it follows a similar process as in step 5, but calls the parseNull method to parse the null value.

  9. If the value of ch is equal to ParseConstants.STRING_START_TOKEN (which represents the start of a string value), it follows a similar process as in step 5, but calls the parseString method to parse the string value.

  10. If the value of ch is one of ParseConstants.NUM_0, ParseConstants.NUM_1, ..., ParseConstants.NUM_9, ParseConstants.MINUS, or ParseConstants.PLUS (which represent the start of a number), it again follows a similar process as in step 5, but calls the parseNumber method to parse the number value. It then checks if the current character in the source is either the end of the JSON array or a comma separator. If it is, it checks if the current character is the end of the JSON array, and if so, it returns true (indicating that the parsing of the array is complete).

  11. If the value of ch is equal to ParseConstants.ARRAY_END_TOKEN, it checks if the startChar variable is equal to ParseConstants.ARRAY_SEP (which represents a trailing comma in the array). If it is, it throws an exception indicating that a trailing comma is not allowed. Otherwise, it moves to the next character in the source and returns true (indicating that the parsing of the array is complete).

  12. If none of the above cases match, it throws an exception indicating that an unexpected character was encountered while parsing the array item.

  13. Finally, the method returns false to indicate that the parsing of the array item is not complete.

The method parseArrayItem is used to parse an item within a JSON array. It takes a CharSource and a TokenEventListener as parameters.

First, it retrieves the current character from the CharSource and skips any whitespace characters. Then, it enters a switch statement based on the retrieved character.

  • If the character indicates the start of an object ({), it invokes the parseObject method to further parse the object, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of another array ([), it invokes the parseArray method to further parse the nested array, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of a boolean true, it invokes the parseTrue method to parse the boolean value, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of a boolean false, it invokes the parseFalse method to parse the boolean value, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of a null value, it invokes the parseNull method to parse the value, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of a string ("), it invokes the parseString method to parse the string value, and notifies the TokenEventListener of the start and end of the array item.
  • If the character indicates the start of a number (0-9, minus sign, plus sign), it invokes the parseNumber method to parse the number value, and notifies the TokenEventListener of the start and end of the array item. It then checks if the current character is either the end of the array or a separator, and if so, it returns true to indicate that the parsing of the array item is complete.
  • If the character indicates the end of the array (]), it checks if the previous character was a separator. If so, it throws an exception for a trailing comma. Otherwise, it advances to the next character and returns true to indicate the end of the parsing.

If none of the above cases match, it throws an exception for an unexpected character in the array item.

Finally, if the parsing is not complete, it returns false.

private void parseNumber(final CharSource source, final TokenEventListener event)

private void parseNumber(final CharSource source, final TokenEventListener event) {
    final int startIndex = source.getIndex();
    final NumberParseResult numberParse = source.findEndOfNumber();
    final int tokenType = numberParse.wasFloat() ? TokenTypes.FLOAT_TOKEN : TokenTypes.INT_TOKEN;
    event.start(tokenType, startIndex, source);
    event.end(tokenType, numberParse.endIndex(), source);
}

The parseNumber method in the JsonEventStrictParser class is responsible for parsing a number from a given character source and notifying the event listener about the parsed token.

Here is a step-by-step description of what the parseNumber method does:

  1. Retrieve the current index in the character source using source.getIndex() and store it in startIndex variable.
  2. Utilize the findEndOfNumber method from the CharSource class to locate the end position of the number being parsed. This method returns a NumberParseResult object, which contains information about whether the number was parsed as a float or an integer.
  3. Determine the token type based on whether the number was parsed as a float or an integer. If it was a float, assign TokenTypes.FLOAT_TOKEN to the tokenType variable. Otherwise, assign TokenTypes.INT_TOKEN.
  4. Notify the event listener that parsing of the number is starting by invoking event.start() method and passing the tokenType, startIndex, and the original source as arguments.
  5. Notify the event listener that parsing of the number has ended by invoking event.end() method and passing the tokenType, the end index of the parsed number obtained from numberParse.endIndex(), and the original source as arguments.

Overall, the parseNumber method scans the character source for numbers, determines their types, and informs the event listener of the start and end positions of the parsed numbers using the appropriate token types.

The method parseNumber in class JsonEventStrictParser is used to parse a number from a CharSource object.

  • It first gets the starting index of the number from the CharSource using source.getIndex().
  • Then, it uses the findEndOfNumber method on the CharSource to determine the end of the number and obtain a NumberParseResult object.
  • Based on whether the number was a float or an integer, it assigns the appropriate token type TokenTypes.FLOAT_TOKEN or TokenTypes.INT_TOKEN.
  • It then calls the start and end methods on the TokenEventListener object event to notify the start and end of the number token, passing the token type, start index, end index, and the CharSource object.

sequence diagram

private boolean parseKey(final CharSource source, final TokenEventListener event)

private boolean parseKey(final CharSource source, final TokenEventListener event) {
    final char startChar = source.getCurrentChar();
    int ch = source.nextSkipWhiteSpace();
    event.start(TokenTypes.ATTRIBUTE_KEY_TOKEN, source.getIndex(), source);
    boolean found = false;
    switch(ch) {
        case ParseConstants.STRING_START_TOKEN:
            final int strStartIndex = source.getIndex();
            event.start(TokenTypes.STRING_TOKEN, strStartIndex + 1, source);
            final int strEndIndex;
            if (objectsKeysCanBeEncoded) {
                strEndIndex = source.findEndOfEncodedString();
            } else {
                strEndIndex = source.findEndString();
            }
            found = true;
            event.end(TokenTypes.STRING_TOKEN, strEndIndex, source);
            break;
        case ParseConstants.OBJECT_END_TOKEN:
            if (startChar == ParseConstants.OBJECT_ATTRIBUTE_SEP) {
                throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
            }
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        event.end(TokenTypes.ATTRIBUTE_KEY_TOKEN, source.getIndex(), source);
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

Method: parseKey

This method is defined in the JsonEventStrictParser class within the io.nats.jparse.parser.event package. It takes two parameters: a CharSource object named source and a TokenEventListener object named event. The method returns a boolean value.

Step 1: Store the starting character of the source

  • Get the starting character from the source object and store it in a local variable called startChar.

Step 2: Move to the next non-whitespace character in the source

  • Call the nextSkipWhiteSpace() method on the source object to move to the next non-whitespace character.
  • Store the resulting character in a local variable named ch.

Step 3: Start the ATTRIBUTE_KEY_TOKEN event

  • Call the start() method on the event object, passing TokenTypes.ATTRIBUTE_KEY_TOKEN as the token type, the current index of the source, and the source object itself.

Step 4: Check the type of the character

  • Use a switch statement to check the type of the character ch.

Step 5: Handle STRING_START_TOKEN case

  • If ch is equal to ParseConstants.STRING_START_TOKEN:
    1. Store the current index of the source object in a local variable called strStartIndex.
    2. Call the start() method on the event object, passing TokenTypes.STRING_TOKEN as the token type, (strStartIndex + 1) as the start index, and the source object itself.
    3. Determine the end index of the string based on the value of objectsKeysCanBeEncoded.
    4. If objectsKeysCanBeEncoded is true, call the findEndOfEncodedString() method on the source object to find the end index of the string.
    5. If objectsKeysCanBeEncoded is false, call the findEndString() method on the source object to find the end index of the string.
    6. Set the found variable to true.
    7. Call the end() method on the event object, passing TokenTypes.STRING_TOKEN as the token type, the end index of the string, and the source object itself.

Step 6: Handle OBJECT_END_TOKEN case

  • If ch is equal to ParseConstants.OBJECT_END_TOKEN:
    • Check if the startChar is equal to ParseConstants.OBJECT_ATTRIBUTE_SEP.
    • If true, throw an UnexpectedCharacterException with the message "Unexpected character found" and the source object.
    • Return true.

Step 7: Handle default case

  • If none of the above cases match, throw an UnexpectedCharacterException with the message "Unexpected character found" and the source object.

Step 8: Find the end of the object or attribute separator

  • Call the findObjectEndOrAttributeSep() method on the source object and store the result in a boolean variable named done.

Step 9: Check if the object is not done and a key is found

  • If done is false and found is true, call the end() method on the event object, passing TokenTypes.ATTRIBUTE_KEY_TOKEN as the token type, the current index of the source, and the source object itself.

Step 10: Check if a key is found but the object is done

  • If found is true and done is true, throw an UnexpectedCharacterException with the message "Not found" and the source object.

Step 11: Return the value of the done variable

  • This method returns the value of the done variable, indicating whether the parsing is done or not.

The parseKey method in the JsonEventStrictParser class is used to parse a key from a JSON input using a strict parsing approach.

Here's a step-by-step breakdown of how the method works:

  1. It takes a CharSource object, which represents the source of characters to be parsed, and a TokenEventListener object, which listens to the events generated during parsing.

  2. The method starts by capturing the current character of the source and reads the next character, skipping whitespace.

  3. It notifies the event listener that it is starting to parse an attribute key token.

  4. It evaluates the type of character read:

    • If the character is a string start token ("), it signifies the start of a string. The method then captures the starting index of the string and notifies the event listener that it is starting to parse a string token. It then identifies the end of the string, considering whether object keys can be encoded or not, and notifies the event listener that it has ended the string token.

    • If the character is an object end token (}), it checks if the start character of the key is the object attribute separator (:). If it is not, it throws an UnexpectedCharacterException indicating that an unexpected character was found while parsing the key. If it is, the method returns true, indicating that the parsing of the key is complete.

    • If none of the above conditions are met, it throws an UnexpectedCharacterException indicating that an unexpected character was found while parsing the key.

  5. If the parsing is not yet complete and a valid key was found, the method attempts to find either the end of the object or the attribute separator.

  6. If the parsing is not yet complete, but a valid key was found, the method notifies the event listener that it has ended the attribute key token.

  7. If the parsing is complete but a valid key was found, it throws an UnexpectedCharacterException indicating that the key was not found.

  8. Finally, the method returns whether the parsing is complete or not.

This method is useful for parsing individual keys from a JSON input and generating events for further processing.

private boolean parseValue(final CharSource source, final TokenEventListener event)

private boolean parseValue(final CharSource source, final TokenEventListener event) {
    int ch = source.nextSkipWhiteSpace();
    event.start(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
    switch(ch) {
        case ParseConstants.OBJECT_START_TOKEN:
            parseObject(source, event);
            break;
        case ParseConstants.ARRAY_START_TOKEN:
            parseArray(source, event);
            break;
        case ParseConstants.TRUE_BOOLEAN_START:
            parseTrue(source, event);
            break;
        case ParseConstants.FALSE_BOOLEAN_START:
            parseFalse(source, event);
            break;
        case ParseConstants.NULL_START:
            parseNull(source, event);
            break;
        case ParseConstants.STRING_START_TOKEN:
            parseString(source, event);
            break;
        case ParseConstants.NUM_0:
        case ParseConstants.NUM_1:
        case ParseConstants.NUM_2:
        case ParseConstants.NUM_3:
        case ParseConstants.NUM_4:
        case ParseConstants.NUM_5:
        case ParseConstants.NUM_6:
        case ParseConstants.NUM_7:
        case ParseConstants.NUM_8:
        case ParseConstants.NUM_9:
        case ParseConstants.MINUS:
        case ParseConstants.PLUS:
            parseNumber(source, event);
            break;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, ch);
    }
    source.skipWhiteSpace();
    switch(source.getCurrentChar()) {
        case ParseConstants.OBJECT_END_TOKEN:
            event.end(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
            return true;
        case ParseConstants.OBJECT_ATTRIBUTE_SEP:
            event.end(TokenTypes.ATTRIBUTE_VALUE_TOKEN, source.getIndex(), source);
            return false;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, source.getCurrentChar());
    }
}

The parseValue method in the JsonEventStrictParser class is used to parse a single JSON value from the input source.

Here is a step-by-step description of what the method is doing based on its body:

  1. The method starts by reading the next character from the input source and skipping any white space characters.

  2. It then notifies the token event listener that a new attribute value token has started.

  3. The method enters a switch case based on the value of the current character: a. If the current character is the start of an object ('{'), it calls the parseObject method to parse the object. b. If the current character is the start of an array ('['), it calls the parseArray method to parse the array. c. If the current character is the start of the true boolean value ('t'), it calls the parseTrue method to parse the boolean value true. d. If the current character is the start of the false boolean value ('f'), it calls the parseFalse method to parse the boolean value false. e. If the current character is the start of the null value ('n'), it calls the parseNull method to parse the null value. f. If the current character is the start of a string ('"'), it calls the parseString method to parse the string value. g. If the current character is a number or a minus or plus sign, it calls the parseNumber method to parse the numeric value. h. If none of the above cases match, it throws an UnexpectedCharacterException with an error message indicating that an unexpected character was encountered while parsing the value.

  4. After parsing the value, the method skips any white space characters in the input source.

  5. The method then enters another switch case based on the current character in the input source: a. If the current character is the end of an object ('}'), it notifies the token event listener that the attribute value token has ended and returns true. b. If the current character is the object attribute separator (','), it notifies the token event listener that the attribute value token has ended and returns false. c. If none of the above cases match, it throws an UnexpectedCharacterException with an error message indicating that an unexpected character was encountered while parsing the value.

In summary, the parseValue method in the JsonEventStrictParser class is responsible for parsing a single JSON value from the input source and notifying the token event listener about the start and end of the value token.

The parseValue method in the JsonEventStrictParser class is responsible for parsing a JSON value from a given source and notifying the token event listener about the parsed value.

The method starts by skipping any white space characters and then starts the parsing by identifying the type of the value based on the first character. It uses a switch statement to handle different types of values such as objects, arrays, booleans, null, strings, and numbers.

After parsing the value, it skips any remaining white space characters and checks the next character to determine the end of the value. If the next character is an object closing token (}), it notifies the token event listener and returns true. If the next character is an object attribute separator (:), it notifies the token event listener and returns false.

If the next character does not match any of the expected values, it throws an UnexpectedCharacterException with an appropriate error message.

Overall, the parseValue method is a critical part of the JSON parsing process and is responsible for parsing different types of JSON values and notifying the token event listener accordingly.

sequence diagram

private void parseObject(final CharSource source, final TokenEventListener event)

private void parseObject(final CharSource source, final TokenEventListener event) {
    levelCheck(source);
    event.start(TokenTypes.OBJECT_TOKEN, source.getIndex(), source);
    boolean done = false;
    while (!done) {
        done = parseKey(source, event);
        if (!done)
            done = parseValue(source, event);
    }
    if (source.getCurrentChar() != ParseConstants.OBJECT_END_TOKEN) {
        throw new UnexpectedCharacterException("Parsing Object", "Unexpected character", source, source.getCurrentCharSafe());
    }
    source.next();
    event.end(TokenTypes.OBJECT_TOKEN, source.getIndex(), source);
}

The method parseObject is defined in the class io.nats.jparse.parser.event.JsonEventStrictParser. It takes two parameters: source, which is of type CharSource, and event, which is of type TokenEventListener.

Here is a step-by-step description of what the method parseObject is doing:

  1. It performs a level check, which is not explained in the given code snippet.
  2. It starts the OBJECT_TOKEN event by calling event.start() method, passing the token type, the index of the source, and the source itself as parameters.
  3. It initializes a boolean variable called done to false.
  4. It enters a while loop that continues until done is true. This loop is used to parse the key-value pairs of the object.
  5. Inside the loop, it calls the parseKey method passing the source and event as arguments, and assigns the returned value to done.
  6. If parseKey did not parse a key successfully, it calls the parseValue method passing the source and event as arguments, and assigns the returned value to done.
  7. If source.getCurrentChar() is not the OBJECT_END_TOKEN (which indicates the end of the object), it throws an UnexpectedCharacterException. The exception message states that it was parsing an object and encountered an unexpected character. It also includes the source and the current character as additional information.
  8. It advances the source to the next character by calling source.next().
  9. It ends the OBJECT_TOKEN event by calling event.end() method, passing the token type, the index of the source, and the source itself as parameters.

The method parseObject is responsible for parsing an object in the JSON input, iterating over its key-value pairs, and notifying the TokenEventListener about the start and end of the object.

The parseObject method is used to parse a JSON object string. It takes in a source input and a token event listener as parameters.

The method starts by performing a level check to ensure the input is valid. It then calls the event.start method to notify the listener that a JSON object token has been encountered.

A while loop is used to efficiently parse the key-value pairs within the object. Inside the loop, it first calls parseKey method to parse the key and notifies the event listener. If the key parsing is not yet done, it then calls parseValue method to parse the corresponding value and notifies the listener. This process continues until all key-value pairs have been parsed.

After the object's closing token is encountered, the method checks if it matches the expected object end token. If it doesn't, an exception is thrown. Otherwise, it calls source.next() to move the cursor to the next character and calls event.end to notify the listener that the object parsing is complete.

In summary, the parseObject method reads a JSON object string, validates its structure, parses its key-value pairs, and notifies an event listener about the start and end of the object token.

sequence diagram

private void levelCheck(CharSource source)

private void levelCheck(CharSource source) {
    nestLevel++;
    if (nestLevel > ParseConstants.NEST_LEVEL) {
        throw new UnexpectedCharacterException("Next level violation", "Too many levels " + nestLevel, source);
    }
}

The method levelCheck in the JsonEventStrictParser class is responsible for checking the nesting level of a JSON document. Here is a step-by-step description of what this method does:

  1. It receives a CharSource object as a parameter, which represents the source of characters for parsing the JSON document.

  2. The method increments the nestLevel variable by one. This variable keeps track of the current nesting level in the JSON document.

  3. It checks if the nestLevel is greater than a constant value ParseConstants.NEST_LEVEL. This constant represents the maximum allowed nesting level in the JSON document.

  4. If the nestLevel exceeds the maximum allowed level, the method throws an UnexpectedCharacterException with the message "Next level violation" and additional information about exceeding the allowed level.

  5. If the nestLevel is within the allowed limit, the method continues execution without any further action.

Overall, the levelCheck method ensures that the nesting level of the JSON document does not exceed a certain threshold and throws an exception if the limit is violated. This check helps maintain the integrity and reliability of the parsing process.

The levelCheck method in the JsonEventStrictParser class is responsible for checking the nesting level of a JSON document.

This method increments the nestLevel variable by one, indicating that a new nested level has been encountered. If the nestLevel exceeds the maximum allowed nesting level defined in ParseConstants.NEST_LEVEL, an UnexpectedCharacterException is thrown, indicating that the JSON document has violated the maximum nesting level constraint.

The exception message includes the details of the violation, specifically mentioning the current nestLevel, and the source of the JSON document where the violation occurred.

sequence diagram

io.nats.jparse.examples

class diagram

EmployeeDeptMain

The EmployeeDeptMain class is a public class that serves as the entry point for the employee and department management system. It provides functionality for managing employees and departments within an organization.

public static void main(String... args)

public static void main(String... args) {
    try {
        final String json = getJson();
        //final var engineeringEmployees = Path.atPath("departments[0].employees", json).asCollection().asArray();
        final File file = new File("./src/test/resources/json/depts.json");
        final RootNode rootNode = Json.toRootNode(Sources.fileSource(file));
        final ArrayNode engineeringEmployees = Path.atPath("departments[0].employees", rootNode).asCollection().asArray();
        final Node cindy = Path.atPath("[2]", engineeringEmployees);
        final Node cindyName = Path.atPath("[2].firstName", engineeringEmployees);
        final Node cindyId = Path.atPath(".id", cindy);
        final Node manager = Path.atPath("[2].manager", engineeringEmployees);
        System.out.println("      " + engineeringEmployees.toJsonString());
        System.out.println("      " + cindy.toJsonString());
        if (manager.asScalar().booleanValue()) {
            System.out.printf("This employee %s is a manager %s \n", cindyName, manager);
        }
        if (cindyName.asScalar().equalsString("Cindy")) {
            System.out.printf("The employee's name is  Cindy %s \n", cindyId);
        }
        if (cindyName instanceof CharSequence) {
            System.out.println("cirhyeName is a CharSequence");
        }
        if (cindyId instanceof Number) {
            System.out.println("cindyId is a Number");
        }
        if (engineeringEmployees instanceof List) {
            System.out.println("engineeringEmployees is a List " + engineeringEmployees.getClass().getName());
        }
        if (cindy instanceof Map) {
            System.out.println("cindy is a Map " + cindy.getClass().getName());
        }
        final Optional<ObjectNode> rick = engineeringEmployees.stream().map(node -> node.asCollection().asObject()).filter(objectNode -> objectNode.getString("firstName").equals("Rick")).findFirst();
        rick.ifPresent(node -> {
            System.out.println("Found  " + node);
            exploreNode(node);
        });
        final Optional<ObjectNode> rick2 = engineeringEmployees.findObjectNode(objectNode -> objectNode.getString("firstName").equals("Rick"));
        rick2.ifPresent(node -> {
            System.out.println("Found  " + node);
            exploreNode(node);
        });
        final List<Employee> employees = engineeringEmployees.mapObjectNode(on -> new Employee(on.getString("firstName"), on.getString("lastName"), on.getString("dob"), on.getBoolean("manager"), on.getInt("id"), on.getInt("managerId")));
        employees.forEach(System.out::println);
        final ArrayNode departmentsNode = Path.atPath("departments", json).asCollection().asArray();
        final List<Department> departments = departmentsNode.mapObjectNode(on -> new Department(on.getString("departmentName"), on.getArrayNode("employees").mapObjectNode(en -> new Employee(en.getString("firstName"), en.getString("lastName"), en.getString("dob"), en.getBoolean("manager"), en.getInt("id"), en.getInt("managerId")))));
        departments.forEach(System.out::println);
        parseBadJson();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

The main method in class io.nats.jparse.examples.EmployeeDeptMain performs the following steps:

  1. Calls the getJson() method to retrieve a JSON string.
  2. Creates a File object pointing to the JSON file located at "./src/test/resources/json/depts.json".
  3. Converts the file into a RootNode using the Json.toRootNode() method.
  4. Retrieves the "employees" array from the "departments[0]" path in the RootNode and assigns it to the engineeringEmployees variable.
  5. Retrieves the third object in the engineeringEmployees array and assigns it to the cindy variable.
  6. Retrieves the value of the "firstName" property from the cindy node and assigns it to the cindyName variable.
  7. Retrieves the value of the "id" property from the cindy node using a relative path and assigns it to the cindyId variable.
  8. Retrieves the value of the "manager" property from the engineeringEmployees array at index 2 and assigns it to the manager variable.
  9. Prints the engineeringEmployees array in JSON format.
  10. Prints the cindy node in JSON format.
  11. Checks if the manager node is a boolean value and prints a message if it is.
  12. Checks if the cindyName node is equal to the string "Cindy" and prints a message if it is.
  13. Checks if the cindyName node is an instance of CharSequence and prints a message if it is.
  14. Checks if the cindyId node is an instance of Number and prints a message if it is.
  15. Checks if the engineeringEmployees object is an instance of List and prints a message with its class name if it is.
  16. Checks if the cindy object is an instance of Map and prints a message with its class name if it is.
  17. Uses stream operations to find the first object in engineeringEmployees with "firstName" equal to "Rick" and assigns it to the rick optional object. If found, it prints the found node and calls exploreNode() method with the found node.
  18. Uses the findObjectNode() method to find the object in engineeringEmployees with "firstName" equal to "Rick".
  19. Assigns the found node to the rick2 optional object. If found, it prints the found node and calls exploreNode() method with the found node.
  20. Maps each object in engineeringEmployees to an Employee object using the corresponding properties and collects them into a list.
  21. Prints each Employee object in the employees list.
  22. Retrieves the "departments" array from the JSON string and assigns it to the departmentsNode variable.
  23. Maps each object in departmentsNode to a Department object using the corresponding properties and collects them into a list.
  24. Prints each Department object in the departments list.
  25. Calls the parseBadJson() method.
  26. Catches any exception that occurs during the execution and prints the stack trace.

sequence diagram

private static void parseBadJson()

private static void parseBadJson() {
    //RootNode rootNode = toRootNode(niceJson("'hi mom"));
    //RootNode rootNode = toRootNode(niceJson("['aabc', \n  'def', \n 123f3f.9]"));
    //RootNode rootNode = toRootNode(niceJson("{'name':'rick', \n\n\n" +
    //        " 'age':19, 'height'=10}"));
}

The method parseBadJson() defined in class io.nats.jparse.examples.EmployeeDeptMain is used to parse invalid JSON input.

Here are the steps involved in this method:

  1. Commented code: The method body contains multiple lines of commented code. These lines represent different variations of invalid JSON inputs.

  2. RootNode object creation: The commented lines instantiate a RootNode object named rootNode. However, since these lines are commented out, they do not execute.

  3. toRootNode() method: The toRootNode() method is called with an argument, niceJson(), which is used to simulate invalid JSON data. However, since these lines are commented out, the toRootNode() method is not executed.

  4. niceJson() method: The niceJson() method is not defined in the given code snippet. Therefore, it is not involved in the execution of the parseBadJson() method.

In summary, the parseBadJson() method is designed to illustrate the parsing of invalid JSON inputs. However, since all the relevant code is commented out, this method does not perform any actual JSON parsing in its current state.

sequence diagram

private static void exploreNode(ObjectNode node)

private static void exploreNode(ObjectNode node) {
    int id = node.getInt("id");
    String name = node.getString("firstName");
    String dob = node.getString("dob");
    boolean isManager = node.getBoolean("manager");
    int managerId = node.getInt("managerId");
    System.out.printf("%d %s %s %s %d \n", id, name, dob, isManager, managerId);
    final NumberNode idNode = node.getNumberNode("id");
    final StringNode nameNode = node.getStringNode("firstName");
    final StringNode dobNode = node.getStringNode("dob");
    final BooleanNode isManagerNode = node.getBooleanNode("manager");
    final NumberNode managerIdNode = node.getNumberNode("managerId");
    System.out.printf("%s %s %s %s %s \n", idNode, nameNode, dobNode, isManagerNode, managerIdNode);
    System.out.printf("%d %s %s %s %d \n", idNode.intValue(), nameNode.toString(), dobNode.toString(), isManagerNode.booleanValue(), managerIdNode.intValue());
}

The exploreNode method is defined in the EmployeeDeptMain class in the package io.nats.jparse.examples. Here is a step-by-step description of what the method does:

  1. The method takes an ObjectNode as a parameter, which is a JSON object representing an employee's details.

  2. The method extracts the values from specific fields of the JSON object using various get methods such as getInt, getString, and getBoolean. The extracted values are assigned to local variables.

  3. The method prints the extracted values using System.out.printf method call in a formatted string.

  4. The method then retrieves the same values again but this time using the getXNode methods, where X represents the type of each particular value (e.g., getNumberNode, getStringNode). The retrieved values are assigned to variables of respective types (NumberNode, StringNode, etc.).

  5. The method prints the retrieved XNode values using System.out.printf method call in a formatted string.

  6. Finally, the method prints the converted values from the XNode variables to their primitive types (e.g., intValue, toString, booleanValue) using System.out.printf method call in a formatted string.

sequence diagram

Department

The Department class is a final Class that represents a department in an organization. It provides functionality to manage and manipulate department related information.

CloudEventMain

The CloudEventMain class is a public class that serves as the entry point for processing cloud events. It contains the main method that initializes and runs the cloud event processing logic.

public static void main(String... args)

public static void main(String... args) {
    final File file = new File("./src/test/resources/cloudevents/glossaryEvent.json");
    final ObjectNode objectNode = Json.toRootNode(Sources.fileSource(file)).asObject();
    if (objectNode.getNode("subject").equalsContent("glossaryFeed")) {
        final String id = objectNode.getString("id");
        final String type = objectNode.getString("type");
        final String data = Json.serializeToString(objectNode.getNode("data"));
        System.out.printf("%s %s %s \n%s\n", "glossaryFeed", id, type, data);
    }
}

The main method in the CloudEventMain class is performing the following steps based on its body:

  1. It defines a File object with the file path "./src/test/resources/cloudevents/glossaryEvent.json".

  2. It uses the Sources.fileSource method to read the contents of the file and converts it to an ObjectNode using the Json.toRootNode method.

  3. It checks if the value of the "subject" field in the objectNode is equal to "glossaryFeed" using the equalsContent method.

  4. If the condition in step 3 is true, it retrieves the values of the "id", "type", and "data" fields from the objectNode.

  5. It serializes the value of the "data" field to a JSON string using the Json.serializeToString method.

  6. It prints the values of "glossaryFeed", "id", "type", and "data" to the console using the System.out.printf method. The values are formatted in the following format: "glossaryFeed id type data".

Please note that the actual execution of the code may have additional steps or error handling that is not mentioned in the provided code snippet.

sequence diagram

Employee

The Employee class is a final class that represents an employee. It provides various methods and attributes to manage and retrieve information about an employee.

io.nats.jparse

class diagram

Validation

The Validation class is a public class used for implementing validation logic in software applications. It provides methods and functionality to validate various types of input data or user inputs. This class serves as a utility for performing validation checks and returning appropriate results or error messages.

public static void main(String... args)

public static void main(String... args) {
    try {
        final File file = new File("./src/test/resources/validation/");
        System.out.println("Event Strict Parser");
        final boolean showPass = false;
        final boolean showFail = false;
        validateParser(file, (JsonParser) Json.builder().setStrict(true).buildEventParser(), showFail, showPass);
        System.out.println("Strict Parser");
        final JsonParser jsonParser = Json.builder().setStrict(true).build();
        validateParser(file, jsonParser, showFail, showPass);
    } catch (Throwable ex) {
        ex.printStackTrace();
    }
}

The main method defined in the io.nats.jparse.Validation class performs the following steps:

  1. It begins by trying to execute the code within a try block.

  2. It creates a new File object and assigns it the path "./src/test/resources/validation/".

  3. It prints the message "Event Strict Parser" to the console.

  4. It initializes two boolean variables, showPass and showFail, and assigns each a value of false.

  5. It calls the validateParser method with the file object and a JsonParser object created using the Json.builder() method. The JsonParser is configured to use strict parsing. The showFail and showPass variables are also passed to the validateParser method.

  6. It prints the message "Strict Parser" to the console.

  7. It creates a new JsonParser object by calling the Json.builder() method again and configuring it to use strict parsing.

  8. It calls the validateParser method again with the file object, the newly created JsonParser, and the showFail and showPass variables.

  9. If any exception or error occurs during the execution of the try block, it is caught by the catch block.

  10. If an exception or error is caught, it is printed to the console using the printStackTrace method. sequence diagram

private static void validateParser(File file, JsonParser jsonParser, boolean showFail, boolean showPass)

private static void validateParser(File file, JsonParser jsonParser, boolean showFail, boolean showPass) {
    int[] result1 = validate(file, "y_", showFail, showPass, jsonParser);
    int[] result2 = validate(file, "i_", showFail, showPass, jsonParser);
    int[] result3 = validate(file, "n_", showFail, showPass, jsonParser);
    System.out.printf("Passed Mandatory %d Failed %d \n", result1[0], result1[1]);
    System.out.printf("Passed Optional %d Failed %d \n", result2[0], result2[1]);
    System.out.printf("Passed Garbage %d Failed %d \n", result3[0], result3[1]);
}

The method validateParser is defined in the class io.nats.jparse.Validation. It takes four parameters: file, jsonParser, showFail, and showPass.

The method has the following steps:

  1. It calls the validate method with the parameters file, "y_", showFail, showPass, and jsonParser. The result is stored in an integer array result1.
  2. It calls the validate method again with the parameters file, "i_", showFail, showPass, and jsonParser. The result is stored in an integer array result2.
  3. It calls the validate method once more with the parameters file, "n_", showFail, showPass, and jsonParser. The result is stored in an integer array result3.
  4. It prints the number of passed and failed validations for the "Mandatory" category using the values in result1.
  5. It prints the number of passed and failed validations for the "Optional" category using the values in result2.
  6. It prints the number of passed and failed validations for the "Garbage" category using the values in result3.

The output will be in the following format:

Passed Mandatory <number of passed validations> Failed <number of failed validations>
Passed Optional <number of passed validations> Failed <number of failed validations>
Passed Garbage <number of passed validations> Failed <number of failed validations>

Please note that the actual values for the number of passed and failed validations will depend on the implementation of the validate method. sequence diagram

private static int[] validate(File file, String match, boolean showFail, boolean showPass, JsonParser jsonParser)

private static int[] validate(File file, String match, boolean showFail, boolean showPass, JsonParser jsonParser) {
    try {
        int pass = 0;
        int error = 0;
        for (File listFile : file.listFiles()) {
            if (listFile.getName().startsWith(match)) {
                final CharSource cs = Sources.fileSource(listFile);
                final String jsonString = cs.toString();
                //System.out.println("TESTING " + listFile);
                try {
                    RootNode root = jsonParser.parse(jsonString);
                    if (showPass) {
                        System.out.println("PASS! " + listFile);
                        System.out.println(cs);
                        System.out.println();
                    }
                    pass++;
                } catch (Exception ex) {
                    //ex.printStackTrace();
                    if (showFail) {
                        System.out.println("FAIL! " + listFile);
                        System.out.println(cs);
                        System.out.println();
                    }
                    error++;
                }
            }
        }
        return new int[] { pass, error };
    } catch (Throwable ex) {
        ex.printStackTrace();
    }
    return new int[] { -1, -1 };
}

The validate method is a static method defined in the io.nats.jparse.Validation class. Here is a step-by-step description of what the method does:

  1. It takes the following parameters as input:

    • file: A File object representing a directory containing files to validate.
    • match: A String used to match filenames in the directory.
    • showFail: A boolean indicating whether to show failure messages.
    • showPass: A boolean indicating whether to show success messages.
    • jsonParser: A JsonParser object used to parse the JSON data.
  2. The method starts by initializing two variables: pass and error to keep track of the number of successful and failed validations, respectively.

  3. It then iterates over each File object in the directory using an enhanced for loop.

  4. Inside the loop, it checks if the filename of the current File object starts with the match string.

  5. If the filename matches, it creates a CharSource object using the listFile and reads its content into a String variable jsonString.

  6. It then tries to parse the jsonString using the provided jsonParser object.

  7. If the parsing is successful, it increments the pass variable.

  8. If the showPass flag is true, it prints a success message along with the listFile and CharSource objects.

  9. If the parsing fails and an exception is thrown, it increments the error variable.

  10. If the showFail flag is true, it prints a failure message along with the listFile and CharSource objects.

  11. After iterating through all the files in the directory, the method returns an int array containing the number of successful and failed validations.

  12. If any Throwable is caught during the execution of the method, it prints the stack trace of the exception.

  13. If any exception or error occurs during the execution of the method, it returns an int array with -1 for both the successful and failed validations.

That's a step-by-step description of what the validate method does based on its provided body. sequence diagram

TestKeyLookUp

The TestKeyLookUp class is a public class that provides functionality for looking up test keys.

@Test

void testStringKey()

@Test
void testStringKey() {
    final JsonParser parser = Json.builder().build();
    final String json1 = "'1'";
    final String json2 = "{'1':{'2':1}}";
    final Node key = getJsonRoot(parser, json1).getNode();
    final ObjectNode map = getJsonRoot(parser, json2).getObjectNode();
    final Optional<Node> value = map.getNode(key);
    assertTrue(value.isPresent());
    final Node innerMap = value.get();
    assertTrue(innerMap instanceof ObjectNode);
    final ObjectNode innerObject = (ObjectNode) innerMap;
    final long v = innerObject.getLong("2");
    assertEquals(1, v);
}

The testStringKey method in the io.nats.jparse.TestKeyLookUp class is a unit test for the StringKey functionality. Here is a step-by-step description of what this method is doing:

  1. Creating a new instance of the JsonParser by calling Json.builder().build().
  2. Defining two String variables, json1 and json2, containing the JSON strings "'1'" and "{'1':{'2':1}}" respectively.
  3. Calling the getJsonRoot method, passing the JsonParser and json1 as arguments, to obtain the root node of the JSON from json1. Storing the result in a Node variable called key.
  4. Calling the getJsonRoot method again, passing the JsonParser and json2 as arguments, to obtain the root node of the JSON from json2. Storing the result in a ObjectNode variable called map.
  5. Calling the getNode method on the map object, passing the key as the argument, to retrieve the value associated with the given key. Storing the result in an Optional<Node> variable called value.
  6. Asserting that value is present (i.e., not null).
  7. Calling the get method on value to retrieve the actual Node object. Storing the result in a Node variable called innerMap.
  8. Asserting that innerMap is an instance of ObjectNode.
  9. Casting innerMap to ObjectNode and storing the result in an ObjectNode variable called innerObject.
  10. Calling the getLong method on innerObject, passing "2" as the argument, to retrieve the value associated with the key "2". Storing the result in a long variable called v.
  11. Asserting that the value of v is equal to 1. sequence diagram

Json

The Json class provides static utility methods for working with JSON data. It includes methods for parsing JSON data into various data structures, converting data structures to JSON, and scanning JSON data for tokens.

public static String niceJson(String json)

public static String niceJson(String json) {
    char[] chars = json.toCharArray();
    StringBuilder sb = new StringBuilder(chars.length);
    for (char c : chars) {
        if (c == '\'') {
            sb.append('"');
        } else if (c == '`') {
            sb.append('\\');
        } else {
            sb.append(c);
        }
    }
    return sb.toString();
}
```## Path

The `Path` class provides utility methods for working with JSON paths. It includes methods for parsing JSON paths, looking up nodes at specified paths, and converting paths to `PathNode` objects. This class is used in conjunction with other classes such as `Node`, `Json`, and `PathNode` to manipulate and traverse JSON data.
### public static Node atPath(final PathNode path, final Node rootNode) 
```java
public static Node atPath(final PathNode path, final Node rootNode) {
    Iterator<PathElement> iterator = path.iterator();
    Node node = rootNode;
    PathElement pathElement = null;
    try {
        while (iterator.hasNext()) {
            pathElement = iterator.next();
            switch(node.type()) {
                case OBJECT:
                    final ObjectNode objectNode = (ObjectNode) node;
                    final CharSequence key = pathElement.asKey().toCharSequence();
                    node = objectNode.getNode(key);
                    break;
                case ARRAY:
                    final ArrayNode arrayNode = (ArrayNode) node;
                    node = arrayNode.getNodeAt(pathElement.asIndex().intValue());
                    break;
                default:
                    if (node.isCollection()) {
                        node = node.asCollection().getNode(pathElement.asKey().toCharSequence());
                    } else {
                        throw new PathException("Looking up Path", "Path not found at " + path + " path element key " + pathElement.asKey().toString(), node.charSource(), node.rootElementToken().startIndex);
                    }
            }
        }
    } catch (Exception ex) {
        throw new IllegalStateException("Path not found at " + path + " path element index " + pathElement.value());
    }
    return node;
}

atPath Method Description

The atPath method is a static method defined in the Path class in the io.nats.jparse package. This method is used to navigate and retrieve a specific node in a JSON structure based on a given path.

Method Signature

public static Node atPath(final PathNode path, final Node rootNode)

Parameters

  • path (type: PathNode): The path to be followed to retrieve the desired node.
  • rootNode (type: Node): The root node of the JSON structure from which the desired node is to be retrieved.

Return Value

  • Node: The node found at the given path.

Method Logic

  • The method starts by obtaining an iterator for the given path.
  • It then initializes the node variable with the rootNode parameter.
  • A pathElement variable is also initialized to null.

The method then enters a try-catch block, where the main logic of the method is described:

  • Inside a while loop, the method checks if there are more elements in the path using the iterator's hasNext() method.
  • If there are more elements, it retrieves the next path element using the iterator's next() method and assigns it to the pathElement variable.
  • Based on the type of the current node, the method follows one of the three cases:
    • If the node is of type OBJECT, it casts the node to an ObjectNode, retrieves the key from the path element, and uses it to get the corresponding node from the object using the getNode(key) method. This new node is assigned to the node variable.
    • If the node is of type ARRAY, it casts the node to an ArrayNode and retrieves the node at the index specified by the path element using the getNodeAt(index) method. This new node is assigned to the node variable.
    • If none of the above cases match, it checks if the node is a collection using the isCollection() method. If it is, it retrieves the node based on the path element's key using the getNode(key) method. Otherwise, it throws a PathException indicating that the path was not found.
  • The while loop continues until all elements in the path have been processed.

If there is any exception thrown during the execution of the while loop, it is caught in the catch block:

  • The catch block throws an IllegalStateException indicating that the path was not found based on the path and pathElement parameters.

Finally, the method returns the node found at the given path.

Note: The specific implementation details of the Node and PathNode classes are not provided in this code snippet. The behavior of the method may vary depending on the actual implementation of these classes. sequence diagram

BenchMark

The BenchMark class is annotated with @State(value = Scope.Benchmark), which signifies that it represents a state object for benchmarking purposes. This class is intended to be used in benchmarking scenarios, providing a state for measuring the performance of methods or code snippets.

public static void main(String... args) throws Exception

public static void main(String... args) throws Exception {
    try {
        JsonParser fastParser = Json.builder().setStrict(false).build();
        fastParser.parse(doublesJsonData).asArray().getDoubleArray();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    System.out.println("DONE");
    //
    //        try {
    //            long startTime = System.currentTimeMillis();
    //
    //            JsonParser fastParser = Json.builder().setStrict(false).build();
    //
    //            for (int i = 0; i < 10_500_000; i++) {
    //
    //                final ObjectNode objectNode = fastParser.parse(glossaryEvent).asObject();
    //                if (objectNode.getNode("subject").equalsContent("glossaryFeed")) {
    //                    final String id = objectNode.getStringNode("id").toUnencodedString();
    //                    final String type = objectNode.getStringNode("type").toUnencodedString();
    //                    final String description = objectNode.getStringNode("description").toUnencodedString();
    //                    final String data = Json.serializeToString(objectNode.getNode("data"));
    //
    //                    if (i % 1_000_000 == 0) {
    //                        System.out.printf("Elapsed time %s %s \n", ((System.currentTimeMillis() - startTime) / 1000.0), data);
    //                    }
    //                }
    //
    //                final JsonIterator iter = JsonIterator.parse(glossaryEvent);
    //                final Map<String, Object> map = iter.read(mapTypeRefJI);
    //                if (map.get("subject").equals("glossaryFeed")) {
    //                    final String id =  (String) map.get("id");
    //                    final String type = (String) map.get("type");
    //                    final String description = (String) map.get("description");
    //                    final String data = JsonStream.serialize(map.get("data"));
    //                    if (i % 1_000_000 == 0) {
    //                        System.out.printf("Elapsed time %s %s \n", ((System.currentTimeMillis() - startTime) / 1000.0), data);
    //                    }
    //                }
    //                final HashMap<String, Object> map = mapper.readValue(glossaryEvent, mapTypeRef);
    //                if (map.get("subject").equals("glossaryFeed")) {
    //                    final String id =  (String) map.get("id");
    //                    final String type = (String) map.get("type");
    //                    final String description = (String) map.get("description");
    //                    final String data = mapper.writeValueAsString(map.get("data"));
    //                }
    //
    //
    //            }
    //            System.out.println("Total Elapsed time " + ((System.currentTimeMillis() - startTime) / 1000.0));
    //
    //        } catch (Exception ex) {
    //            ex.printStackTrace();
    //        }
}

The main method in class io.nats.jparse.BenchMark is divided into multiple sections, each performing a different task. Let's break it down step by step:

  1. The method begins with a try-catch block to handle any exceptions that may occur during execution.

  2. Inside the try block, a JsonParser object named fastParser is created using the Json.builder() method. The setStrict(false) method is called to configure the parser to be non-strict.

  3. The fastParser object then parses the JSON data stored in the doublesJsonData variable. The parsed data is treated as an array and the getDoubleArray() method is called to retrieve an array of doubles.

  4. If an exception occurs during parsing, it is caught in the catch block and the stack trace is printed.

  5. After the catch block, the string "DONE" is printed to the console.

  6. The code is then commented out, starting from try { long startTime = System.currentTimeMillis(); and ending with System.out.println("Total Elapsed time " + ((System.currentTimeMillis() - startTime) / 1000.0)); }.

  7. In the commented out code, a startTime variable is initialized with the current system time.

  8. Inside a loop that iterates 10,500,000 times, the following actions are performed:

    a. The glossaryEvent is parsed by the fastParser object and the result is treated as an ObjectNode.

    b. If the "subject" field of the objectNode is equal to "glossaryFeed", the following fields are extracted from the objectNode: "id", "type", "description" and "data". The data field is serialized as a string using Json.serializeToString() method.

    c. If the loop index i is a multiple of 1,000,000, the elapsed time since startTime is printed to the console along with the serialized data.

    d. The glossaryEvent is parsed by JsonIterator and the result is stored in a Map<String, Object>.

    e. If the "subject" field of the map is equal to "glossaryFeed", the following fields are extracted: "id", "type", "description" and "data". The data field is serialized as a string using JsonStream.serialize() method.

    f. If the loop index i is a multiple of 1,000,000, the elapsed time since startTime is printed to the console along with the serialized data.

    g. The glossaryEvent is deserialized into a HashMap<String, Object> using an unspecified mapper object and mapTypeRef.

    h. If the "subject" field of the map is equal to "glossaryFeed", the following fields are extracted: "id", "type", "description" and "data". The data field is serialized as a string using mapper.writeValueAsString() method.

  9. Finally, the total elapsed time since startTime is calculated and printed to the console.

  10. If an exception occurs during execution, it is caught in the catch block and the stack trace is printed.

This is the step-by-step description of the main method based on its body. sequence diagram

//

// @Benchmark // public void cloudEventJsonIter(Blackhole bh) throws Exception

//
//    @Benchmark
//    public void cloudEventJsonIter(Blackhole bh) throws Exception{
//        final JsonIterator iter = JsonIterator.parse(glossaryEvent);
//        final Map<String, Object> map = iter.read(mapTypeRefJI);
//        if (map.get("subject").equals("glossaryFeed")) {
//                final String id =  (String) map.get("id");
//                final String type = (String) map.get("type");
//                final String description = (String) map.get("description");
//                final String data = JsonStream.serialize(map.get("data"));
//                bh.consume(new Object[]{id, type, data, description});
//        }
//    }
//
//    @Benchmark
//    public void cloudEventJackson(Blackhole bh) throws JsonProcessingException {
//        final HashMap<String, Object> map = mapper.readValue(glossaryEvent, mapTypeRef);
//        if (map.get("subject").equals("glossaryFeed")) {
//            final String id =  (String) map.get("id");
//            final String type = (String) map.get("type");
//            final String description = (String) map.get("description");
//            final String data = mapper.writeValueAsString(map.get("data"));
//            bh.consume(new Object[]{id, type, data, description});
//        }
//    }
//
//    @Benchmark
//    public void cloudEventJParse(Blackhole bh) throws JsonProcessingException {
//        final ObjectNode objectNode = fastParser.parse(glossaryEvent).asObject();
//        if (objectNode.getNode("subject").equalsContent("glossaryFeed")) {
//            final String id = objectNode.getStringNode("id").toUnencodedString();
//            final String type = objectNode.getStringNode("type").toUnencodedString();
//            final String description = objectNode.getStringNode("description").toUnencodedString();
//            final String data = Json.serializeToString(objectNode.getNode("data"));
//            bh.consume(new Object[]{id, type, data, description});
//        }
//    }
//
@Benchmark
public void jParseFastFloatArray(Blackhole bh) {
    bh.consume(this.fastParser.parse(doublesJsonData).asArray().getFloatArray());
}

The method jParseFastFloatArray is a benchmark method defined in the class io.nats.jparse.BenchMark. It is used to compare the performance of parsing a JSON array of floating-point numbers using a specific parser called fastParser.

Here is a step-by-step description of what the method is doing:

  1. It takes a parameter bh of type Blackhole. This is an object provided by the benchmark framework that can be used to consume the result of the benchmark, preventing the compiler from optimizing away the benchmarked code.

  2. The method executes the following code:

bh.consume(this.fastParser.parse(doublesJsonData).asArray().getFloatArray());
a. The `fastParser` parses the `doublesJsonData`, which is a JSON array of floating-point numbers.
b. The parsed result is treated as an array using the `asArray()` method.
c. The `getFloatArray()` method is called on the parsed array to get an array of floating-point numbers.
d. The result of `getFloatArray()` is consumed by the `Blackhole` object, ensuring that the benchmarked code is not optimized away.
  1. The method does not return any value.

Overall, the jParseFastFloatArray method benchmarks the parsing performance of the fastParser for a JSON array of floating-point numbers and consumes the result using the Blackhole object. sequence diagram

io.nats.jparse.source

class diagram

CharArrayOffsetCharSource

The CharArrayOffsetCharSource class is a concrete implementation of the CharSource abstract class and the ParseConstants interface. It represents a character source backed by a character array, where the starting position of the character sequence is indicated by an offset. This class provides methods for parsing integer, long, float, and double values from the character sequence. It also includes a validation method to check if a substring represents a valid integer. Additionally, it implements an errorDetails method that generates a detailed error message based on the current character being processed and the current parsing state.

public static String debugCharDescription(int c)

public static String debugCharDescription(int c) {
    String charString;
    if (c == ' ') {
        charString = "[SPACE]";
    } else if (c == '\t') {
        charString = "[TAB]";
    } else if (c == '\n') {
        charString = "[NEWLINE]";
    } else if (c == ETX) {
        charString = "ETX";
    } else {
        charString = "'" + (char) c + "'";
    }
    charString = charString + " with an int value of " + c;
    return charString;
}

The debugCharDescription method in the CharArrayOffsetCharSource class is responsible for providing a textual description of a given character based on its ASCII value.

Here is the step-by-step description of what this method does:

  1. The method takes an integer value c as input.
  2. It initializes a variable called charString to hold the description of the character.
  3. It checks if the input character c is equal to a space character. If true, it sets charString to the string "[SPACE]".
  4. If the input character c is a tab character, it sets charString to the string "[TAB]".
  5. If the input character c is a newline character, it sets charString to the string "[NEWLINE]".
  6. If c is equal to a special character identified as ETX, it sets charString to the string "ETX".
  7. If the character c does not match any of the above conditions, it appends the character to charString by converting the ASCII value of c to a character using (char) c. For example, if c is 65, the character 'A' will be appended as "'A'".
  8. Finally, the method appends " with an int value of " and the value of c to the current value of charString.
  9. The method returns the final value of charString, which represents the description of the character based on its ASCII value. sequence diagram

@Override

public int next()

@Override
public int next() {
    if (index + 1 >= sourceEndIndex) {
        index = sourceEndIndex;
        return ETX;
    }
    return data[++index];
}

Method: next()

This method is overridden from the parent class to provide the functionality of iterating over the characters in the underlying char array source.

Steps:
  1. Check if the current index (index) incremented by 1 is greater than or equal to the end index of the source array (sourceEndIndex).
    • If true, set index to sourceEndIndex and return the ETX constant, which indicates the end of the stream.
  2. Otherwise, increment index by 1 and return the character at the updated index in the data array.

Note: The data array is the char array source, and index is a variable that keeps track of the current position in the array. The ETX constant represents the end of the stream. sequence diagram

@Override

public int findAttributeEnd()

@Override
public int findAttributeEnd() {
    int index = this.index;
    final char[] data = this.data;
    final int end = this.sourceEndIndex;
    loop: for (; index < end; index++) {
        char ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
                this.index = index;
                break loop;
        }
    }
    return index;
}

The findAttributeEnd method is defined in the CharArrayOffsetCharSource class and is used to find the end index of an attribute. Here is a step-by-step description of how the method works:

  1. The method overrides the findAttributeEnd method defined in the parent class.

  2. It initializes the local variable index with the value of the instance variable this.index. This variable represents the current index position in the character array.

  3. It creates a local variable data and assigns it the value of the instance variable this.data. This variable represents the character array from which the attribute is being extracted.

  4. It creates a local variable end and assigns it the value of the instance variable this.sourceEndIndex. This variable represents the end index of the character array.

  5. It enters a loop that iterates from the current index position (index) to the end index (end) of the character array.

  6. Within each iteration of the loop, it retrieves the character at the current index position (index) from the character array and assigns it to the variable ch.

  7. It checks the value of ch using a switch statement.

  8. If ch matches any of the case values NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, or ATTRIBUTE_SEP, it updates the instance variable this.index with the value of index and breaks out of the loop labeled loop.

  9. After the loop ends, it returns the value of index, which represents the end index of the attribute.

In summary, the findAttributeEnd method iterates through a character array starting from the current index position and looks for specific characters that indicate the end of an attribute. Once a matching character is found, it updates the instance variable this.index and returns the end index. sequence diagram

@Override

public boolean findChar(char c)

@Override
public boolean findChar(char c) {
    int index = this.index;
    final char[] data = this.data;
    final int end = sourceEndIndex;
    for (; index < end; index++) {
        if (data[index] == c) {
            this.index = index;
            return true;
        }
    }
    return false;
}

The findChar method is used to search for a specific character within a character array. Here is a step-by-step description of what the method does:

  1. The method receives a character c as a parameter.
  2. It assigns the current index of the CharArrayOffsetCharSource object to the local variable index.
  3. It retrieves the character array data from the CharArrayOffsetCharSource object.
  4. It retrieves the end index of the source from the CharArrayOffsetCharSource object and assigns it to the local variable end.
  5. It starts a loop that iterates from the current index index until the end index end.
  6. Within each iteration, it checks if the character at position index within the data array is equal to the character c.
  7. If a match is found, it updates the current index of the CharArrayOffsetCharSource object to the value of index and returns true.
  8. If no match is found after iterating through the entire array, it simply returns false.
  9. The method is marked as @Override, indicating that it overrides a method from a superclass or interface.

Overall, the findChar method performs a linear search for a specific character c within the character array data. It updates the current index of the CharArrayOffsetCharSource object if a match is found and returns true. Otherwise, it returns false indicating that the character was not found. sequence diagram

@Override

public void checkForJunk()

@Override
public void checkForJunk() {
    int index = this.index;
    final char[] data = this.data;
    final int end = this.sourceEndIndex;
    int ch = ETX;
    for (; index < end; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                throw new UnexpectedCharacterException("Junk", "Unexpected extra characters", this);
        }
    }
}

The checkForJunk method, defined in the io.nats.jparse.source.CharArrayOffsetCharSource class, is used to check for any junk characters in the provided character array.

Here is a step-by-step description of what the method does:

  1. It starts by initializing variables: index with the current index position in the character array, data with the provided character array, end with the end index of the character array, and ch with the ASCII value of ETX (End-of-Text).

  2. It enters a for loop that iterates from the current index to the end index of the character array.

  3. Inside the loop, it assigns the value of data[index] to ch, which represents the current character being checked.

  4. It uses a switch statement to check the value of ch against predefined cases. The cases being checked are:

    a) NEW_LINE_WS: Represents a new line whitespace character. b) CARRIAGE_RETURN_WS: Represents a carriage return whitespace character. c) TAB_WS: Represents a tab whitespace character. d) SPACE_WS: Represents a space whitespace character.

    If ch matches any of the above cases, the loop continues to the next iteration without executing any further code. This means that if the current character is a whitespace character, it is considered valid and not classified as junk.

  5. If the value of ch does not match any of the cases above, it means that an unexpected character (considered as junk) has been encountered in the character array. In this case, an UnexpectedCharacterException is thrown with the message "Junk" and "Unexpected extra characters". The this reference, representing the current instance of the CharArrayOffsetCharSource class, is passed as an argument to the exception.

  6. The for loop continues to the next iteration, and steps 3-5 are repeated until all characters in the character array have been checked.

By the end of the method, if no UnexpectedCharacterException is thrown, it means that there are no junk characters found in the provided character array. sequence diagram

@Override

public int nextSkipWhiteSpace()

@Override
public int nextSkipWhiteSpace() {
    int index = this.index + 1;
    final char[] data = this.data;
    final int endIndex = sourceEndIndex;
    int ch = ETX;
    loop: for (; index < endIndex; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                break loop;
        }
    }
    this.index = index;
    return index == endIndex ? ETX : ch;
}

The nextSkipWhiteSpace method is defined in the CharArrayOffsetCharSource class in the io.nats.jparse.source package.

This method is used to skip white space characters in the data array starting from the current index. It returns the index of the next non-white space character, or ETX (end of text) if there are no more characters.

Here is a step-by-step description of what the method does:

  1. Increment index by 1 to start from the next character.
  2. Store the reference to the data array in a local variable named data.
  3. Store the value of sourceEndIndex in a local variable named endIndex.
  4. Create a variable ch and initialize it with the ETX value.
  5. Start a loop that will iterate from the current index until the endIndex.
  6. Inside the loop, assign the character at data[index] to ch.
  7. Use a switch statement to check the value of ch.
    • If ch is equal to NEW_LINE_WS or CARRIAGE_RETURN_WS or TAB_WS or SPACE_WS, continue to the next iteration of the loop.
    • Otherwise, break out of the loop.
  8. Update the index with the value of index.
  9. Return ETX if the index is equal to endIndex, otherwise return the value of ch.

This method effectively skips any white space characters (including new lines, carriage returns, tabs, and spaces) in the data array until a non-white space character is encountered. It then returns the index of that non-white space character. If there are no more characters after skipping white space, it returns the ETX value. sequence diagram

@Override

public char skipWhiteSpace()

@Override
public char skipWhiteSpace() {
    int index = this.index;
    final char[] data = this.data;
    final int endIndex = sourceEndIndex;
    char ch;
    loop: for (; index < endIndex; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                break loop;
        }
    }
    this.index = index;
    return data[index];
}

The skipWhiteSpace method in the CharArrayOffsetCharSource class is used to skip any white space characters (spaces, tabs, new lines, and carriage returns) in the character array.

Here is a step-by-step description of what the method is doing:

  1. It first initializes a local variable index with the current value of this.index, which represents the current position in the character array.
  2. It also initializes a local variable data with the reference to the character array this.data that contains the source data.
  3. It gets the endIndex from the sourceEndIndex instance variable, which represents the end position in the character array.
  4. It declares a variable ch to store the current character being examined.
  5. It enters a loop that iterates over the character array starting from the current index till the endIndex.
  6. Inside the loop, it checks the character at the current index data[index] against a set of white space characters: NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, and SPACE_WS.
  7. If the character matches any of the white space characters, the loop continues to the next iteration without performing any action.
  8. If the character does not match any of the white space characters, it breaks out of the loop using the break loop; statement.
  9. After the loop completes, it updates the index instance variable with the current index value, representing the new position after skipping white space.
  10. Finally, it returns the character at that position data[index] from the character array.

So, the skipWhiteSpace method essentially advances the position in the character array to the next non-white space character and returns that character. sequence diagram

@Override

public String toEncodedStringIfNeeded(int startIndex, int endIndex)

@Override
public String toEncodedStringIfNeeded(int startIndex, int endIndex) {
    final int start = startIndex + sourceStartIndex;
    final int end = endIndex + sourceStartIndex;
    if (CharArrayUtils.hasEscapeChar(data, start, end)) {
        return getEncodedString(startIndex, endIndex);
    } else {
        return this.getString(startIndex, endIndex);
    }
}

The toEncodedStringIfNeeded method in the io.nats.jparse.source.CharArrayOffsetCharSource class is used to convert a portion of a character array into a string representation if it contains escape characters.

Here is a step-by-step description of what the method does:

  1. The method overrides the toEncodedStringIfNeeded method from the ancestor class.

  2. The method takes two parameters - startIndex and endIndex, which denote the range of characters to be converted to a string.

  3. It calculates the actual start index and end index within the data character array by adding the startIndex and endIndex to the sourceStartIndex instance variable.

  4. It checks if the portion of the character array from the calculated start index to end index (inclusive) contains any escape characters using the CharArrayUtils.hasEscapeChar method.

  5. If the portion contains escape characters, it calls the getEncodedString method with the startIndex and endIndex parameters to obtain the encoded string representation.

  6. If the portion does not contain any escape characters, it calls the getString method with the startIndex and endIndex parameters to obtain the regular string representation.

  7. Finally, it returns the obtained string representation either with escape characters encoded or without any encoding based on the presence of escape characters within the portion of the character array specified by the startIndex and endIndex. sequence diagram

@Override

public NumberParseResult findEndOfNumberFast()

@Override
public NumberParseResult findEndOfNumberFast() {
    int i = index + 1;
    char ch = 0;
    final char[] data = this.data;
    final int endIndex = this.sourceEndIndex;
    for (; i < endIndex; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i - sourceStartIndex, false);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case DECIMAL_POINT:
                index = i;
                return findEndOfFloatFast();
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponentFast();
            default:
                throw new IllegalStateException("Unexpected character " + ch + " at index " + index);
        }
    }
    index = i;
    return new NumberParseResult(i - sourceStartIndex, false);
}

The method findEndOfNumberFast in the class io.nats.jparse.source.CharArrayOffsetCharSource is used to find the end position of a number within a character array.

Here is a step-by-step breakdown of what this method does:

  1. It initializes the local variable i to the value of index + 1.

  2. It initializes the local variable ch to 0.

  3. It assigns the value of data (a char array) to the local variable data.

  4. It assigns the value of sourceEndIndex (the end index of the source) to the local variable endIndex.

  5. It starts a loop from i until it reaches endIndex.

  6. Inside the loop, it retrieves the character at index i from the data array and assigns it to ch.

  7. It uses a switch statement to handle different cases based on the value of ch.

    • If ch matches any of the specified cases (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, ARRAY_END_TOKEN), it updates the value of index to i and returns a new NumberParseResult object with the length of the number (i - sourceStartIndex) and a boolean indicating that the number does not have a decimal point or exponent.

    • If ch matches any digit character (NUM_0, NUM_1, NUM_2, ..., NUM_9), it continues to the next iteration of the loop.

    • If ch matches the decimal point character (DECIMAL_POINT), it updates the value of index to i and calls the findEndOfFloatFast method to find the end of a floating-point number.

    • If ch matches the exponent marker characters (EXPONENT_MARKER, EXPONENT_MARKER2), it updates the value of index to i and calls the parseFloatWithExponentFast method to parse the number with exponent notation.

    • If none of the above cases match, it throws an IllegalStateException with an error message indicating the unexpected character and its index.

  8. After the loop ends, it updates the value of index to i.

  9. It returns a new NumberParseResult object with the length of the number (i - sourceStartIndex) and a boolean indicating that the number does not have a decimal point or exponent. sequence diagram

private NumberParseResult findEndOfFloatFast()

private NumberParseResult findEndOfFloatFast() {
    int i = index + 1;
    char ch = 0;
    final char[] data = this.data;
    final int endIndex = this.sourceEndIndex;
    for (; i < endIndex; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i - sourceStartIndex, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponentFast();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Float Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i - sourceStartIndex, true);
}

The findEndOfFloatFast method in the io.nats.jparse.source.CharArrayOffsetCharSource class is responsible for finding the end of a float number in a JSON source.

Here is a step-by-step description of what the method does:

  1. Initialize the variable i with the value of index + 1. This indicates the starting index of the float number.
  2. Initialize the variable ch with the value 0.
  3. Get a reference to the data array and store it in a local variable.
  4. Get the endIndex of the source and store it in a local variable.
  5. Start a loop from i to endIndex.
  6. Get the character at index i from the data array and store it in the variable ch.
  7. Switch on the value of ch to perform different actions based on its type.
    • If ch is a white space character (e.g., NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS), comma (ATTRIBUTE_SEP), curly brace (OBJECT_END_TOKEN), or square bracket (ARRAY_END_TOKEN), set the index to i and return a NumberParseResult object indicating the number of characters parsed and that parsing is successful.
    • If ch is a digit character (NUM_0, NUM_1, NUM_2, ..., NUM_9), continue with the loop to parse the next character.
    • If ch is an exponent marker character (EXPONENT_MARKER, EXPONENT_MARKER2), set the index to i and call the parseFloatWithExponentFast() method to parse the number with exponent notation.
    • If ch is any other character, throw an UnexpectedCharacterException indicating that an unexpected character is encountered while parsing a JSON float number.
  8. After the loop completes, set the index to i to indicate the end index of the float number.
  9. Return a NumberParseResult object indicating the number of characters parsed and a flag indicating a successful parse.

Note: The specific values (e.g., NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, etc.) mentioned in the switch cases are assumed to be constants defined elsewhere in the code. sequence diagram

private NumberParseResult parseFloatWithExponentFast()

private NumberParseResult parseFloatWithExponentFast() {
    int i = index + 1;
    char ch = 0;
    int signOperator = 0;
    final char[] data = this.data;
    final int end = sourceEndIndex;
    for (; i < end; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i - sourceStartIndex, true);
            case MINUS:
            case PLUS:
                signOperator++;
                if (signOperator > 1) {
                    throw new IllegalStateException("Too many sign operators when parsing exponent of float");
                }
                break;
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            default:
                throw new IllegalStateException("Unexpected character " + ch + " at index " + index);
        }
    }
    index = i;
    return new NumberParseResult(i - sourceStartIndex, true);
}

Method: parseFloatWithExponentFast()

This method is defined in the io.nats.jparse.source.CharArrayOffsetCharSource class.

Step-by-step description:

  1. Initialize a variable i with the value of index + 1.
  2. Initialize a variable ch with a value of 0.
  3. Initialize a variable signOperator with a value of 0.
  4. Get the character array data from the instance of CharArrayOffsetCharSource.
  5. Get the end index end of the character source.
  6. Enter a loop starting from i until end.
  7. Get the character at index i from the data array and assign it to ch.
  8. Enter a switch statement based on the value of ch:
    • If ch matches any of the following cases:
      • NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, or ARRAY_END_TOKEN,
      • Update the index variable to i and return a new NumberParseResult object with the difference i - sourceStartIndex and a value of true.
    • If ch matches either MINUS or PLUS,
      • Increment the signOperator variable by 1.
      • If signOperator is greater than 1, throw an IllegalStateException with the message "Too many sign operators when parsing exponent of float".
    • If ch matches any of the following cases:
      • NUM_0, NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, or NUM_9,
      • Continue to the next iteration of the loop.
    • If ch does not match any of the above cases, throw an IllegalStateException with the message "Unexpected character ch at index index".
  9. After the loop, update the index variable to i.
  10. Return a new NumberParseResult object with the difference i - sourceStartIndex and a value of true. sequence diagram

@Override

public int findEndOfEncodedStringFast()

@Override
public int findEndOfEncodedStringFast() {
    int i = ++index;
    final char[] data = this.data;
    final int end = sourceEndIndex;
    boolean controlChar = false;
    for (; i < end; i++) {
        char ch = data[i];
        switch(ch) {
            case CONTROL_ESCAPE_TOKEN:
                controlChar = !controlChar;
                continue;
            case STRING_END_TOKEN:
                if (!controlChar) {
                    index = i + 1;
                    return i;
                }
                controlChar = false;
                break;
            default:
                controlChar = false;
                break;
        }
    }
    throw new IllegalStateException("Unable to find closing for String");
}

The findEndOfEncodedStringFast() method, defined in the io.nats.jparse.source.CharArrayOffsetCharSource class, is used to find the closing position of an encoded string within a character array.

Here is a step-by-step description of how the method works:

  1. The method starts by incrementing the index variable by 1 and assigning its value to i.

  2. It then retrieves the character array (data) and the end index (end) from the source.

  3. The method initializes a boolean variable controlChar to false. This variable keeps track of whether a control character (such as an escape character) has been encountered.

  4. A for loop is initiated, iterating through each character in the character array, starting from index i and going up to end.

  5. Within the loop, the current character (ch) is retrieved.

  6. A switch statement is used to check the value of the current character:

    a. If the character is equal to CONTROL_ESCAPE_TOKEN, the controlChar variable is toggled (flipped) by assigning it the opposite value (true becomes false, false becomes true), and the loop continues to the next iteration.

    b. If the character is equal to STRING_END_TOKEN, and controlChar is false (indicating that it's not within a control sequence), then the method updates the index variable to be equal to i + 1, and returns the current value of i. This signifies that the closing position of the encoded string has been found.

    c. If the character does not match either of the above cases, the controlChar variable is set to false, indicating that it is not within a control sequence.

  7. If none of the characters within the loop match the conditions to find the closing position of the encoded string, the method throws an IllegalStateException with a message stating "Unable to find closing for String". This serves as an indication that the closing position could not be found.

In summary, the findEndOfEncodedStringFast() method iterates through a character array, checking each character to determine the closing position of an encoded string. It considers control characters and checks for the end token to determine when the closing position is reached. sequence diagram

private int findEndOfStringControlEncode(int i)

private int findEndOfStringControlEncode(int i) {
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    ch = data[i];
    switch(ch) {
        case CONTROL_ESCAPE_TOKEN:
        case STRING_END_TOKEN:
        case 'n':
        case 'b':
        case '/':
        case 'r':
        case 't':
        case 'f':
            return i;
        case 'u':
            return findEndOfHexEncoding(i);
        default:
            throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
    }
}

The findEndOfStringControlEncode method is defined in the io.nats.jparse.source.CharArrayOffsetCharSource class and takes an integer i as input. Below is a step-by-step description of what the method is doing:

  1. The method begins by declaring a local variable data, which is assigned the value of this.data. this.data refers to a char array that represents the source data being processed.
  2. A local variable length is declared and assigned the length of the data array.
  3. A local variable ch of type char is declared and initialized to 0.
  4. The character at index i of the data array is assigned to ch.
  5. A switch statement is used to perform different actions based on the value of ch.
    • If ch is equal to CONTROL_ESCAPE_TOKEN, STRING_END_TOKEN, 'n', 'b', '/', 'r', 't', or 'f', the method simply returns i. These characters represent the control escape token and various escape sequences commonly found in JSON strings.
    • If ch is equal to 'u', the method calls the findEndOfHexEncoding method with i as an argument and returns the result. This suggests that findEndOfHexEncoding is responsible for finding the end of a hexadecimal encoding, which is likely used to decode Unicode characters in the JSON string.
    • If none of the above cases match, it means that an unexpected character was encountered while trying to find the closing for a string. In this case, an UnexpectedCharacterException is thrown with a descriptive error message, including the current source (this), the unexpected character ch, and the current index i.

That concludes the step-by-step description of the findEndOfStringControlEncode method. sequence diagram

@Override

public int findEndOfEncodedString()

@Override
public int findEndOfEncodedString() {
    int i = ++index;
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case CONTROL_ESCAPE_TOKEN:
                i = findEndOfStringControlEncode(i + 1);
                continue;
            case STRING_END_TOKEN:
                index = i + 1;
                return i;
            default:
                if (ch >= SPACE_WS) {
                    continue;
                }
                throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing JSON Encoded String", "Unable to find closing for String", this, ch, i);
}

The findEndOfEncodedString method is defined in the CharArrayOffsetCharSource class in the io.nats.jparse.source package. Here is a step-by-step description of what this method does:

  1. Declare a local variable i and initialize it with the value of ++index. This increments the value of the index variable by 1 and assigns the updated value to i.
  2. Retrieve the data array from this object. This is the character array that the method operates on.
  3. Get the length of the data array and store it in the length variable.
  4. Declare a variable ch and initialize it with 0.
  5. Start a loop that iterates through the array from the current value of i to the end of the array (length).
  6. Get the character at the current index i from the data array and assign it to ch.
  7. Use a switch statement to perform different actions based on the value of ch:
    • If ch is equal to the CONTROL_ESCAPE_TOKEN constant, call the findEndOfStringControlEncode method with the argument i + 1 and assign its return value to i. This method is not provided, so we can't describe its exact behavior.
    • If ch is equal to the STRING_END_TOKEN constant, update the value of the index variable to i + 1 and return the current value of i.
    • If none of the above conditions are true, check if ch is greater than or equal to the SPACE_WS constant. If it is, continue to the next iteration of the loop.
    • If none of the conditions above are true, throw an UnexpectedCharacterException with a message indicating that an unexpected character was encountered while finding the closing of a string. The exception includes information about the current CharSource, the unexpected character ch, and its position i.
  8. If the loop completes without finding the closing of the string, throw an UnexpectedCharacterException with a message indicating that the closing for the string could not be found. The exception includes information about the current CharSource, the last character ch encountered, and its position i. sequence diagram

private int findEndOfHexEncoding(int index)

private int findEndOfHexEncoding(int index) {
    final char[] data = this.data;
    final int length = data.length;
    if (isHex(data[++index]) && isHex(data[++index]) && isHex(data[++index]) && isHex(data[++index])) {
        return index;
    } else {
        throw new UnexpectedCharacterException("Parsing hex encoding in a string", "Unexpected character", this);
    }
}

The method findEndOfHexEncoding is defined in the class io.nats.jparse.source.CharArrayOffsetCharSource. It takes an index as a parameter and returns an integer.

Here is a step-by-step description of what the method does:

  1. The method starts by getting the data array and its length from the current instance of CharArrayOffsetCharSource.

  2. It then checks if the character at the next index in the data array is a valid hexadecimal character (isHex). It does this repeatedly for the next 4 characters by incrementing the index for each check.

  3. If all 4 characters are valid hexadecimal characters, the method returns the current value of index.

  4. If any of the characters are not valid hexadecimal characters, the method throws an UnexpectedCharacterException. The exception message states that it occurred while parsing a hex encoding in a string, and the unexpected character is included in the message. The this reference is passed as an argument to provide additional context.

In summary, the findEndOfHexEncoding method scans the data array starting from the given index and checks if the next 4 characters form a valid hex encoding. If they do, it returns the index. If not, it throws an exception. sequence diagram

private boolean isHex(char datum)

private boolean isHex(char datum) {
    switch(datum) {
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            return true;
        default:
            return false;
    }
}

The isHex method in the io.nats.jparse.source.CharArrayOffsetCharSource class is used to determine whether a given character is a hexadecimal digit. Here is a step-by-step description of what the method does:

  1. The method takes a single parameter datum, which represents the character to be tested.

  2. The method uses a switch statement to check the value of the datum character.

  3. If the datum character is any of the following characters: 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', or '9', then the method returns true. These characters represent valid hexadecimal digits.

  4. If the datum character is not one of the specified hexadecimal digits, the default case is executed, and the method returns false.

  5. So overall, the isHex method determines whether a character is a hexadecimal digit by checking if it falls within the specified range of valid hexadecimal characters. If the character is within the range, the method returns true; otherwise, it returns false. sequence diagram

@Override

public int findEndString()

@Override
public int findEndString() {
    int i = ++index;
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case STRING_END_TOKEN:
                index = i;
                return i;
            default:
                if (ch >= SPACE_WS) {
                    continue;
                }
                throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing JSON String", "Unable to find closing for String", this, ch, i);
}

This findEndString method is defined in the CharArrayOffsetCharSource class and it overrides the same method from the superclass.

Here is a step-by-step description of what this method does:

  1. It starts by incrementing the index variable.
  2. It retrieves the data array and its length from the class instance.
  3. It declares a ch variable and sets it to 0.
  4. It enters a loop from the incremented index up to the length of the data array.
  5. Inside the loop, it assigns the current character of the data array to ch.
  6. It checks for a switch case where ch is equal to the STRING_END_TOKEN.
    • If so, it updates the index variable to the current index and returns the index.
    • If not, it continues to the next step.
  7. It then checks if ch is greater than or equal to SPACE_WS. If true, it continues to the next iteration of the loop.
  8. If the character doesn't match the switch case and is not greater than SPACE_WS, it throws an UnexpectedCharacterException with the appropriate message, passing in this object, ch, and the current index.
  9. If the loop completes without finding a match, it throws an UnexpectedCharacterException with the appropriate message, passing in this object, ch, and the current index.

Note: The specific values for STRING_END_TOKEN and SPACE_WS are not included in the code snippet provided and may be defined elsewhere in the codebase or imported from another class or module. sequence diagram

@Override

public NumberParseResult findEndOfNumber()

@Override
public NumberParseResult findEndOfNumber() {
    final char startCh = getCurrentChar();
    final int startIndex = index;
    char ch = startCh;
    int i = index + 1;
    final char[] data = this.data;
    final int endIndex = this.sourceEndIndex;
    loop: for (; i < endIndex; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                break loop;
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case DECIMAL_POINT:
                if (startCh == MINUS) {
                    final int numLenSoFar = i - startIndex;
                    if (numLenSoFar == 1) {
                        throw new UnexpectedCharacterException("Parsing JSON Number", "Unexpected character", this, ch, i);
                    }
                }
                index = i;
                return findEndOfFloat();
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponent();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    final int numLength = i - startIndex;
    switch(startCh) {
        case NUM_0:
            if (numLength != 1) {
                throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't start with a 0 ", this, startCh, startIndex);
            }
            break;
        case PLUS:
            throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't start with a plus ", this, startCh, startIndex);
        case MINUS:
            switch(numLength) {
                case 1:
                    throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't be only a minus, number is missing", this, startCh, startIndex);
                case 2:
                    break;
                default:
                    if (data[startIndex + 1] == NUM_0) {
                        throw new UnexpectedCharacterException("Parsing JSON Int Number", "0 can't be after minus sign", this, startCh, startIndex);
                    }
            }
    }
    return new NumberParseResult(i - this.sourceStartIndex, false);
}

The method findEndOfNumber in the class io.nats.jparse.source.CharArrayOffsetCharSource is responsible for finding the end of a number while parsing JSON data. Here is the step-by-step description of what this method does based on its code:

  1. It retrieves the current character from the source and assigns it to the variable startCh.
  2. It sets the starting index to the current index.
  3. It initializes the variable ch with the value of startCh and increments the variable i by 1.
  4. It retrieves the character array data and the end index endIndex from the source.
  5. It enters a loop that iterates over the characters from i to endIndex.
  6. For each character ch in the loop:
    • If the character is one of the following: NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, or ARRAY_END_TOKEN, the loop breaks.
    • If the character is one of the digits NUM_0 to NUM_9, the loop continues.
    • If the character is a DECIMAL_POINT and the startCh is MINUS, it checks if the length of the number is 1. If it is, it throws an exception. Then, it sets the current index to i and returns the result of calling the findEndOfFloat method.
    • If the character is an EXPONENT_MARKER or an EXPONENT_MARKER2, it sets the current index to i and returns the result of calling the parseFloatWithExponent method.
    • If none of the above conditions match, it throws an exception.
  7. It sets the current index to i.
  8. It calculates the length of the number by subtracting the starting index from i and assigns it to numLength.
  9. It checks the value of the startCh character:
    • If it is NUM_0, it checks if numLength is not equal to 1. If it is not, it throws an exception.
    • If it is PLUS, it throws an exception.
    • If it is MINUS, it checks the value of numLength:
      • If it is 1, it throws an exception.
      • If it is 2, it continues.
      • If it is greater than 2 and the character following the MINUS is NUM_0, it throws an exception.
  10. It creates a new NumberParseResult object with the length of the number (i - this.sourceStartIndex) and the value false.
  11. It returns the NumberParseResult object. sequence diagram

private NumberParseResult findEndOfFloat()

private NumberParseResult findEndOfFloat() {
    int i = index + 1;
    char ch = (char) next();
    if (!isNumber(ch)) {
        throw new UnexpectedCharacterException("Parsing float part of number", "After decimal point expecting number but got", this, ch, this.index);
    }
    final char[] data = this.data;
    final int endIndex = this.sourceEndIndex;
    for (; i < endIndex; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i - sourceStartIndex, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponent();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Float Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i - sourceStartIndex, true);
}

The method findEndOfFloat in the CharArrayOffsetCharSource class is used to find the end of a floating-point number in a JSON input string. Here is a step-by-step description of what the method does:

  1. The method initializes a local variable i to the value of index + 1 and retrieves the next character ch from the input stream using the next() method.

  2. It checks if the character ch is not a valid number character. If it's not, it throws an UnexpectedCharacterException, indicating that a number was expected after the decimal point but a different character was encountered.

  3. The method then retrieves the underlying character array data and the end index of the source string.

  4. It enters a loop that iterates from i to endIndex.

  5. Inside the loop, the method checks the current character ch against a series of predefined characters that can signal the end of a floating-point number. These characters include whitespace characters (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS), attribute and array separators (ATTRIBUTE_SEP, ARRAY_SEP), and object and array end tokens (OBJECT_END_TOKEN, ARRAY_END_TOKEN). If a match is found, the method updates the index variable to the current value of i and returns a new NumberParseResult object containing the length of the parsed number as i - sourceStartIndex and a flag indicating that the number is complete (true).

  6. If the current character ch is not one of the predefined end tokens, the method checks if it's a digit (NUM_0 to NUM_9). If it is, the loop continues to the next iteration. This allows the method to handle numbers with digits after the decimal point.

  7. If the current character ch is an exponent marker (EXPONENT_MARKER or EXPONENT_MARKER2), the method updates the index variable to the current value of i and returns the result of calling the parseFloatWithExponent() method. This method handles parsing floating-point numbers with exponent notation.

  8. If none of the above conditions are met, the method throws an UnexpectedCharacterException, indicating that an unexpected character was encountered while parsing the JSON float number.

  9. After the loop completes without finding an end token, the method updates the index variable to the current value of i and returns a new NumberParseResult object containing the length of the parsed number as i - sourceStartIndex and a flag indicating that the number is complete (true). sequence diagram

private boolean isNumber(final char ch)

private boolean isNumber(final char ch) {
    switch(ch) {
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
            return true;
        default:
            return false;
    }
}

The isNumber method in the CharArrayOffsetCharSource class is used to determine whether a given character is a number. Here is a step-by-step description of how the method works:

  1. The method takes a char parameter called ch.

  2. It uses a switch statement to check the value of the ch character.

  3. The case statements in the switch statement represent the numbers 0 to 9.

  4. If the value of ch matches any of the cases (i.e., it is a number), the method returns true.

  5. If the value of ch does not match any of the cases (i.e., it is not a number), the method returns false.

That's the step-by-step description of how the isNumber method in the CharArrayOffsetCharSource class works. sequence diagram

private NumberParseResult parseFloatWithExponent()

private NumberParseResult parseFloatWithExponent() {
    char ch = (char) next();
    if (!isNumberOrSign(ch)) {
        throw new UnexpectedCharacterException("Parsing exponent part of float", "After exponent expecting number or sign but got", this, ch, this.index);
    }
    if (isSign(ch)) {
        ch = (char) next();
        if (!isNumber(ch)) {
            throw new UnexpectedCharacterException("Parsing exponent part of float after sign", "After sign expecting number but got", this, ch, this.index);
        }
    }
    int i = index + 1;
    final char[] data = this.data;
    final int endIndex = this.sourceEndIndex;
    for (; i < endIndex; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i - sourceStartIndex, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            default:
                throw new UnexpectedCharacterException("Parsing Float with exponent", "Unable to find closing for Number", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i - sourceStartIndex, true);
}

The method parseFloatWithExponent in the CharArrayOffsetCharSource class is used to parse a floating-point number with an exponent. Below is a step-by-step description of what the method does:

  1. The method starts by fetching the next character from the input stream and assigns it to the variable ch.
  2. It checks if the character ch is a valid number or sign character using the isNumberOrSign method. If it is not, it throws an UnexpectedCharacterException with an appropriate error message.
  3. If the character ch is a sign character, it fetches the next character and assigns it to ch. If the next character is not a number, it throws an UnexpectedCharacterException with an appropriate error message.
  4. It initializes a variable i with the value of index + 1, which represents the next index to start parsing at.
  5. It assigns the internal character array data from the CharArrayOffsetCharSource instance to a local variable data.
  6. It assigns the value of sourceEndIndex to a local variable endIndex, which represents the end index of the source.
  7. It enters a for loop that iterates from i to endIndex.
  8. Inside the loop, it assigns the character at index i in the data array to ch.
  9. It checks if ch is one of the following characters: NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, or ARRAY_END_TOKEN. If it is, it updates the index variable to i and returns a new NumberParseResult object with the length of the parsed number (i - sourceStartIndex) and true indicating success.
  10. If ch is one of the digit characters NUM_0 to NUM_9, it continues the loop iteration without any special action.
  11. If ch does not match any of the above cases, it throws an UnexpectedCharacterException with an appropriate error message.
  12. After the loop exits, it updates the index variable to i and returns a new NumberParseResult object with the length of the parsed number (i - sourceStartIndex) and true indicating success.

Overall, the method iterates through the characters starting from the initial index and checks if each character is a valid part of the exponent part of a floating-point number. It stops parsing when it encounters a character that is not part of the exponent or reaches the end of the input. sequence diagram

private boolean isNumberOrSign(char ch)

private boolean isNumberOrSign(char ch) {
    switch(ch) {
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            return true;
        default:
            return false;
    }
}

isNumberOrSign Method

The isNumberOrSign method is defined in the io.nats.jparse.source.CharArrayOffsetCharSource class. It is a private method that takes a single character as input and returns a boolean value.

Purpose

The purpose of this method is to determine whether a given character is a number or a mathematical sign (either a minus or plus symbol).

Method Body

The method contains a switch-case statement that checks the input character against several predefined constants representing the digits 0-9, as well as the minus and plus signs.

  • If the input character matches any of these constants, the method will return true, indicating that the character is a number or a sign.
  • If the input character does not match any of the constants, the method will return false, indicating that the character is not a number or a sign.

Here is the step-by-step description of the isNumberOrSign method based on its body:

  1. Declare a private boolean method named isNumberOrSign that takes a single character ch as input.

  2. Start a switch-case statement to check the value of ch.

  3. In each case, compare the value of ch to the following constants:

    • NUM_0, representing the character '0'
    • NUM_1, representing the character '1'
    • NUM_2, representing the character '2'
    • NUM_3, representing the character '3'
    • NUM_4, representing the character '4'
    • NUM_5, representing the character '5'
    • NUM_6, representing the character '6'
    • NUM_7, representing the character '7'
    • NUM_8, representing the character '8'
    • NUM_9, representing the character '9'
    • MINUS, representing the minus sign '-'
    • PLUS, representing the plus sign '+'
  4. If ch matches any of the above constants, return true to indicate that ch is a number or a sign.

  5. If ch does not match any of the above constants, return false to indicate that ch is not a number or a sign.

  6. End the method. sequence diagram

private boolean isSign(char ch)

private boolean isSign(char ch) {
    switch(ch) {
        case MINUS:
        case PLUS:
            return true;
        default:
            return false;
    }
}

The isSign method in the io.nats.jparse.source.CharArrayOffsetCharSource class is used to determine whether a given character is a sign symbol. It follows these steps:

  1. The method takes a character ch as a parameter.
  2. It enters a switch statement, which checks the value of ch.
  3. If ch is equal to the MINUS constant or the PLUS constant (presumably defined in the class or imported from another class), the method returns true.
  4. If ch does not match any of the case values, the method returns false.

To summarize, the isSign method checks if a character is a sign symbol (- or +) and returns true if it is, or false if it is not. sequence diagram

@Override

public int findFalseEnd()

@Override
public int findFalseEnd() {
    if (this.data[++index] == 'a' && this.data[++index] == 'l' && this.data[++index] == 's' && this.data[++index] == 'e') {
        return ++index - sourceStartIndex;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON False Boolean", "Unexpected character", this);
    }
}

The findFalseEnd method in the io.nats.jparse.source.CharArrayOffsetCharSource class is defined as follows:

  1. The method starts by incrementing the index variable and checks if the character at the updated index is equal to 'a'.
  2. If the character is 'a', the method then increments the index variable again and checks if the character at the updated index is equal to 'l'.
  3. If the character is 'l', the method again increments the index variable and checks if the character at the updated index is equal to 's'.
  4. If the character is 's', the method once more increments the index variable and checks if the character at the updated index is equal to 'e'.
  5. If all of the characters 'a', 'l', 's', and 'e' are found consecutively, the method returns the value of the incremented index variable minus the sourceStartIndex variable.
  6. If any of the characters are not found consecutively or the end of the source is reached before finding the complete sequence, an UnexpectedCharacterException is thrown, with the message "Parsing JSON False Boolean" and "Unexpected character", and the instance of the CharArrayOffsetCharSource class is passed as an argument to the exception. sequence diagram

@Override

public int findTrueEnd()

@Override
public int findTrueEnd() {
    if (this.data[++index] == 'r' && this.data[++index] == 'u' && this.data[++index] == 'e') {
        return ++index - sourceStartIndex;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON True Boolean", "Unexpected character", this);
    }
}

The method findTrueEnd is implemented in the class io.nats.jparse.source.CharArrayOffsetCharSource. Its purpose is to find the end position of a boolean value "true" within a JSON source string.

Here is a step-by-step description of what the method does:

  1. Increment the index value by 1 and check if the character at the new index position in the data array is equal to 'r'.
  2. If the character at the new index position is 'r', increment the index value by 1 again and check if the character at the new index position is equal to 'u'.
  3. If the character at the new index position is 'u', increment the index value by 1 once more and check if the character at the new index position is equal to 'e'.
  4. If all the characters 'r', 'u', and 'e' are found one after another, it means the end of the boolean value "true" has been found.
    • Return the value of ++index - sourceStartIndex, which represents the length of the substring containing the boolean value "true".
  5. If any of the characters is not found in the expected sequence, it means there is an unexpected character, and an exception of type UnexpectedCharacterException is thrown.
    • The exception message will indicate that an unexpected character was encountered while parsing the JSON True Boolean value.
    • The exception will also contain a reference to the CharArrayOffsetCharSource object that encountered the unexpected character.

This method is useful for parsing a JSON source string and identifying the end position of the boolean value "true". sequence diagram

@Override

public boolean findObjectEndOrAttributeSep()

@Override
public boolean findObjectEndOrAttributeSep() {
    int i = index;
    char ch = 0;
    final char[] data = this.data;
    final int end = sourceEndIndex;
    for (; i < end; i++) {
        ch = data[i];
        switch(ch) {
            case OBJECT_END_TOKEN:
                this.index = i + 1;
                return true;
            case ATTRIBUTE_SEP:
                this.index = i;
                return false;
        }
    }
    throw new UnexpectedCharacterException("Parsing Object Key", "Finding object end or separator", this);
}

The findObjectEndOrAttributeSep method is defined in the CharArrayOffsetCharSource class in the io.nats.jparse.source package. It is used to find the end of an object or the separator within the source data.

Here is a step-by-step description of what the method does:

  1. The method overrides the findObjectEndOrAttributeSep method defined in the superclass.
  2. It initializes the local variables i to the current index and ch to 0.
  3. It retrieves the source data array and assigns it to the local variable data.
  4. It retrieves the end index of the source and assigns it to the local variable end.
  5. It starts a loop from the current index i until the end index end.
  6. Within the loop, it retrieves the character at index i from the source data array and assigns it to ch.
  7. It then performs a switch-case on the character ch.
  8. If the character is equal to the object end token, it updates the index to i + 1, indicating that the end of the object has been found, and returns true.
  9. If the character is equal to the attribute separator, it updates the index to i, indicating that the separator has been found, and returns false.
  10. If none of the above cases match, it continues to the next iteration of the loop.
  11. If the loop completes without finding the object end or separator, it throws an UnexpectedCharacterException with an appropriate error message.
  12. The error message specifies that the error occurred while parsing an object key and while finding the object end or separator.
  13. The this reference passed to the exception constructor refers to the current CharArrayOffsetCharSource instance.

This method provides functionality to find the end of an object or the separator within the source data, allowing for efficient parsing of JSON-like data structures. sequence diagram

@Override

public boolean findCommaOrEndForArray()

@Override
public boolean findCommaOrEndForArray() {
    int i = index;
    char ch = 0;
    final char[] data = this.data;
    final int end = sourceEndIndex;
    for (; i < end; i++) {
        ch = data[i];
        switch(ch) {
            case ARRAY_END_TOKEN:
                this.index = i + 1;
                return true;
            case ARRAY_SEP:
                this.index = i;
                return false;
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                throw new UnexpectedCharacterException("Parsing Object Key", "Finding object end or separator", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing Array", "Finding list end or separator", this);
}

The findCommaOrEndForArray() method is defined in the io.nats.jparse.source.CharArrayOffsetCharSource class. It is an override method that returns a boolean value.

Here is a step-by-step description of what the findCommaOrEndForArray() method does based on its body:

  1. Declare a variable i and initialize it with the current index value.
  2. Declare a variable ch and initialize it with 0.
  3. Assign the data array from the class to a local variable data.
  4. Extract the end value from the sourceEndIndex variable.
  5. Start a loop from the current index value (i) to the end value.
  6. Get the current character ch from data[i].
  7. Perform a switch case on ch.
    • If ch is equal to ARRAY_END_TOKEN:
      • Update the class index value to i + 1.
      • Return true to indicate that an array end has been found.
    • If ch is equal to ARRAY_SEP:
      • Update the class index value to i.
      • Return false to indicate that a comma separator has been found.
    • If ch is equal to any of the whitespace characters (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS):
      • Continue to the next iteration of the loop.
    • If ch is not equal to any of the above characters:
      • Throw an UnexpectedCharacterException with the appropriate message, indicating that an unexpected character was found while parsing the object key or finding the object end or separator.
  8. If the loop completes without finding an array end or separator, throw an UnexpectedCharacterException with the appropriate message, indicating that an unexpected character was found while parsing the array or finding the list end or separator. sequence diagram

@Override

public int findNullEnd()

@Override
public int findNullEnd() {
    if (this.data[++index] == 'u' && this.data[++index] == 'l' && this.data[++index] == 'l') {
        return ++index - sourceStartIndex;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON Null", "Unexpected character", this);
    }
}

The findNullEnd method in the io.nats.jparse.source.CharArrayOffsetCharSource class is designed to find the end of the string "null" within the provided character array.

Here is the step-by-step description of what this method does:

  1. The method starts with the index variable pointing to the current position in the character array.

  2. It checks if the character at the next position in the array after index is 'u'.

  3. If the character at the next position is 'u', it then checks if the next character after 'u' is 'l'.

  4. If the character at the next position after 'l' is also 'l', it means that the string "null" has been found.

  5. In that case, the method returns the current index value incremented by 1 and subtracted by sourceStartIndex. This calculation gives the length of the matched "null" string within the character array.

  6. If the characters 'u', 'l', and 'l' are not found in sequence, it means that an unexpected character has been encountered while parsing the JSON string.

  7. In such cases, the method throws an UnexpectedCharacterException, providing a descriptive message indicating that an unexpected character was found.

In summary, the findNullEnd method searches for the string "null" within a character array and returns the length of the matched string, or throws an exception if an unexpected character is encountered. sequence diagram

@Override

public boolean matchChars(final int startIndex, final int endIndex, CharSequence key)

@Override
public boolean matchChars(final int startIndex, final int endIndex, CharSequence key) {
    final int length = endIndex - startIndex;
    final int offset = this.sourceStartIndex;
    int idx = startIndex + offset;
    switch(length) {
        case 1:
            return key.charAt(0) == data[idx];
        case 2:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1];
        case 3:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2];
        case 4:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2] && key.charAt(3) == data[idx + 3];
        case 5:
            return key.charAt(1) == data[idx + 1] && key.charAt(3) == data[idx + 3] && key.charAt(0) == data[idx] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 6:
            return key.charAt(0) == data[idx] && key.charAt(5) == data[idx + 5] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 7:
            return key.charAt(0) == data[idx] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(5) == data[idx + 5] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 8:
            return key.charAt(0) == data[idx] && key.charAt(7) == data[idx + 7] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(5) == data[idx + 5] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(4) == data[idx + 4];
        case 9:
            return key.charAt(0) == data[idx] && key.charAt(8) == data[idx + 8] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1];
        case 10:
            return key.charAt(0) == data[idx] && key.charAt(9) == data[idx + 9] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        case 11:
            return key.charAt(0) == data[idx] && key.charAt(10) == data[idx + 10] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(9) == data[idx + 9] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        case 12:
            return key.charAt(0) == data[idx] && key.charAt(11) == data[idx + 11] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(9) == data[idx + 9] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(10) == data[idx + 10] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        default:
            final int start = 0;
            final int end = length - 1;
            final int middle = length / 2;
            if (key.charAt(start) == data[idx] && key.charAt(end) == data[idx + end] && key.charAt(middle) == data[idx + middle]) {
                for (int i = 1; i < length; i++) {
                    if (key.charAt(i) != data[idx + i]) {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
    }
}

Method: matchChars

This method is defined in the class io.nats.jparse.source.CharArrayOffsetCharSource and is used to match a sequence of characters from the provided startIndex to endIndex with the given key. The method returns a boolean value indicating whether the characters match or not.

Method Signature:

public boolean matchChars(final int startIndex, final int endIndex, CharSequence key)

Parameters:

  • startIndex (int): The starting index of the character sequence to match.
  • endIndex (int): The ending index of the character sequence to match.
  • key (CharSequence): The key to match against the character sequence.

Step-by-step Description:

  1. Calculate the length of the character sequence by taking the difference between the startIndex and endIndex parameters.
  2. Get the offset from the sourceStartIndex in the class.
  3. Initialize the idx variable by adding the startIndex to the offset.
  4. Use a switch statement based on the length of the character sequence to handle different cases:
    • If the length is 1, compare the first character of the key with the character at data[idx] and return the result.
    • If the length is 2, compare the first two characters of the key with the characters at data[idx] and data[idx + 1] respectively and return the result.
    • Repeat the above steps for different cases of length 3 to 12, comparing the characters one by one.
    • For any other length, do the following:
      • Set the start, end, and middle indices based on the length.
      • Check if the first, last, and middle characters of key match the corresponding characters in data[idx] and data[idx + end] and data[idx + middle] respectively.
      • If the characters match, iterate through the remaining characters of key and compare them with the corresponding characters in data[idx + i].
      • If any character mismatch is found, return false.
      • If all characters match, return true.
  5. If none of the above cases match, return false. sequence diagram

public boolean isInteger(int startIndex, int endIndex)

public boolean isInteger(int startIndex, int endIndex) {
    int len = endIndex - startIndex;
    int offset = this.sourceStartIndex;
    final char[] digitChars = data;
    final boolean negative = (digitChars[startIndex] == '-');
    final int cmpLen = negative ? MIN_INT_STR_LENGTH : MAX_INT_STR_LENGTH;
    if (len < cmpLen)
        return true;
    if (len > cmpLen)
        return false;
    final char[] cmpStr = negative ? MIN_INT_CHARS : MAX_INT_CHARS;
    for (int i = 0; i < cmpLen; ++i) {
        int diff = digitChars[startIndex + i + offset] - cmpStr[i];
        if (diff != 0) {
            return (diff < 0);
        }
    }
    return true;
}

The method isInteger in class io.nats.jparse.source.CharArrayOffsetCharSource can be described in the following steps:

  1. Calculate the length of the input range given by endIndex - startIndex and assign it to the variable len.
  2. Set the offset of the source index to the sourceStartIndex of the object.
  3. Assign the character array data to the local variable digitChars.
  4. Check if the first character at the startIndex of digitChars is a negative sign '-' and assign the result to the negative variable.
  5. Determine the comparison length based on whether the number is negative or not. If it's negative, the comparison length should be MIN_INT_STR_LENGTH, otherwise it should be MAX_INT_STR_LENGTH, and assign the result to cmpLen.
  6. If the len is less than cmpLen, return true as it means the number cannot be an integer.
  7. If the len is greater than cmpLen, return false as it means the number cannot be an integer.
  8. Determine the comparison string based on whether the number is negative or not. If it's negative, use MIN_INT_CHARS, otherwise use MAX_INT_CHARS, and assign the result to cmpStr.
  9. Loop through each character in the comparison range (startIndex + i + offset) and compare it with the corresponding character in cmpStr.
  10. Subtract the characters from digitChars and cmpStr and store the difference in the variable diff.
  11. If diff is not equal to zero, return true if diff is less than zero, indicating that the number is less than the comparison string. Otherwise, return false as the number is greater than the comparison string.
  12. If the loop completes without returning, return true as the number is equal to the comparison string. sequence diagram

@Override

public String errorDetails(String message, int index, int ch)

@Override
public String errorDetails(String message, int index, int ch) {
    StringBuilder buf = new StringBuilder(255);
    final char[] array = data;
    buf.append(message).append("\n");
    buf.append("\n");
    buf.append("The current character read is " + debugCharDescription(ch)).append('\n');
    int line = 0;
    int lastLineIndex = 0;
    for (int i = 0; i < index && i < array.length; i++) {
        if (array[i] == '\n') {
            line++;
            lastLineIndex = i + 1;
        }
    }
    int count = 0;
    for (int i = lastLineIndex; i < array.length; i++, count++) {
        if (array[i] == '\n') {
            break;
        }
    }
    buf.append("line number " + (line + 1)).append('\n');
    buf.append("index number " + index).append('\n');
    buf.append("offset index number " + index + sourceStartIndex).append('\n');
    try {
        buf.append(new String(array, lastLineIndex, count)).append('\n');
    } catch (Exception ex) {
        try {
            int start = index = (index - 10 < 0) ? 0 : index - 10;
            buf.append(new String(array, start, index)).append('\n');
        } catch (Exception ex2) {
            buf.append(new String(array)).append('\n');
        }
    }
    for (int i = 0; i < (index - lastLineIndex); i++) {
        buf.append('.');
    }
    buf.append('^');
    return buf.toString();
}

The errorDetails method in the CharArrayOffsetCharSource class is a method that generates an error debug message with details about the error in the source code at a given index.

Here is a step-by-step description of the method:

  1. Create a new StringBuilder called buf to store the error debug message.
  2. Append the input message to buf followed by a new line character ("\n").
  3. Append an empty line to buf.
  4. Append a description of the current character being read, obtained from the debugCharDescription method, to buf.
  5. Initialize two variables: line and lastLineIndex. line is set to 0 and lastLineIndex is set to 0.
  6. Iterate through the character array data starting from index 0 up to the given index (the index of the error in the source code). a. If the current character in the array is a new line character ('\n'), increment line by 1 and update lastLineIndex to the current index plus 1.
  7. Initialize a count variable to 0.
  8. Iterate through the character array data starting from lastLineIndex up to the end of the array. a. If the current character in the array is a new line character ('\n'), break the loop. b. Otherwise, increment count by 1.
  9. Append the line number (calculated as line + 1) to buf followed by a new line character.
  10. Append the index number to buf followed by a new line character.
  11. Append the offset index number (calculated as index + sourceStartIndex) to buf followed by a new line character.
  12. Try to append a substring of the character array data starting from lastLineIndex with a length of count to buf. If an exception occurs, move to the next step.
  13. If an exception occurs during step 12, try to append a substring of the character array data starting from (index - 10 < 0) ? 0 : index - 10 with a length of index to buf. If this also fails, move to the next step.
  14. If an exception occurs during step 13, append the entire character array data to buf.
  15. Append a number of dots equal to (index - lastLineIndex) to buf.
  16. Append a caret (^) character to buf.
  17. Return the resulting string from buf as the error debug message. sequence diagram

Sources

The Sources class provides utility methods for creating CharSource objects from various input sources, such as strings, byte arrays, files, input streams, and readers. It allows you to easily create CharSource objects from different types of input sources, including strings, byte arrays, files, input streams, and readers. It also provides different methods for creating CharSource objects from specific input types, such as CharSequence, String, byte array, char array, CharBuffer, file, input stream, and Reader. The class handles conversions and error handling for each input type, making it convenient to create CharSource objects for different use cases.

public static CharSource charSeqSource(final CharSequence source)

public static CharSource charSeqSource(final CharSequence source) {
    if (source instanceof String) {
        return new CharArrayCharSource((String) source);
    } else {
        return new CharArrayCharSource(source.toString());
    }
}

The charSeqSource method, defined in the Sources class in the io.nats.jparse.source package, is a public static method that takes a CharSequence parameter named source. The method returns a CharSource object based on the input source.

Here is a step-by-step description of what the charSeqSource method does:

  1. If the source parameter is an instance of the String class:

    • Create a new CharArrayCharSource object, passing in the source parameter (which is casted to a String) as the constructor argument.
    • Return the newly created CharArrayCharSource object.
  2. If the source parameter is not an instance of the String class:

    • Convert the source parameter to a string by invoking the toString() method on it.
    • Create a new CharArrayCharSource object, passing in the converted string as the constructor argument.
    • Return the newly created CharArrayCharSource object.

Note: The CharSource class is not defined in the given code snippet. However, it is assumed to be a class or interface that is imported and accessible within the Sources class. sequence diagram

public static CharSource fileSource(final String fileNameSource, final Charset charset)

public static CharSource fileSource(final String fileNameSource, final Charset charset) {
    try {
        return byteSource(Files.readAllBytes(Paths.get(fileNameSource)), charset);
    } catch (IOException e) {
        throw new IllegalStateException(e);
    }
}

The fileSource method in the io.nats.jparse.source.Sources class is responsible for creating a CharSource object based on the contents of a file specified by the fileNameSource parameter.

Here are the steps performed by the fileSource method:

  1. The method takes two parameters: fileNameSource, which specifies the file name or path, and charset, which specifies the character set to use for reading the file.
  2. Inside a try-catch block, the method calls the static method Files.readAllBytes to read all the bytes from the file specified by fileNameSource. This method returns a byte[] array containing the contents of the file.
  3. The byteSource method is then called, passing the byte[] array and the charset parameter. This method creates and returns a CharSource object based on the provided byte array and character set.
  4. If an IOException occurs during the file reading process, the catch block catches the exception and throws a new IllegalStateException with the caught exception as the cause.

In summary, the fileSource method reads the contents of a file specified by fileNameSource and returns a CharSource object representing the file's content using the specified character set. sequence diagram

public static CharSource fileSource(final File fileSource, final Charset charset)

public static CharSource fileSource(final File fileSource, final Charset charset) {
    try {
        return byteSource(Files.readAllBytes(Paths.get(fileSource.getAbsolutePath())), charset);
    } catch (IOException e) {
        throw new IllegalStateException(e);
    }
}

The method fileSource defined in the class io.nats.jparse.source.Sources is used to create a CharSource from a file. Here is a step-by-step description of what the method does based on its body:

  1. The method takes two parameters: fileSource of type File, which represents the file to read, and charset of type Charset, which specifies the character encoding to use for reading the file.

  2. Inside the method, a try-catch block is used to handle any potential IOException that might occur when reading the file.

  3. The method calls the Files.readAllBytes method, passing in the absolute path of the fileSource file. This method reads all the bytes from the file and returns them as a byte array.

  4. The method then calls another method called byteSource, passing in the byte array obtained from the previous step and the charset provided as arguments. It is not clear from the given code snippet what this byteSource method does, but it is likely used to convert the byte array into a CharSource using the specified character encoding.

  5. If an IOException occurs during the file reading process, the catch block is executed. In this case, an IllegalStateException is thrown, wrapping the original IOException that was caught.

  6. The method ends, either returning the resulting CharSource if the file reading process was successful, or throwing an exception if an error occurred. sequence diagram

public static CharSource readerSource(final Reader readerSource)

public static CharSource readerSource(final Reader readerSource) {
    final BufferedReader reader = new BufferedReader(readerSource);
    StringBuilder stringBuilder = new StringBuilder();
    try {
        String s = reader.readLine();
        while (s != null) {
            stringBuilder.append(s).append('\n');
            s = reader.readLine();
        }
    } catch (Exception ex) {
        throw new IllegalStateException(ex);
    } finally {
        try {
            reader.close();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
    return new CharArrayCharSource(stringBuilder.toString());
}

The readerSource method in class io.nats.jparse.source.Sources is used to convert a Reader object into a CharSource object. Here is a step-by-step description of what the method does:

  1. The method takes a Reader object as an input parameter named readerSource.

  2. It creates a new BufferedReader object named reader by passing the readerSource to its constructor.

  3. It creates a StringBuilder object named stringBuilder to store the contents of the reader.

  4. The method surrounds the following code block with a try-catch block to handle any exceptions that may occur:

    String s = reader.readLine();
    while (s != null) {
        stringBuilder.append(s).append('\n');
        s = reader.readLine();
    }
    • It initializes a String variable s with the first line read from the reader using the readLine() method.
    • It enters a while loop that continues until s is null.
    • Inside the loop, it appends the value of s to stringBuilder, followed by a newline character '\n'.
    • It reads the next line from reader and assigns it to s.
  5. If any exception occurs during the execution of the code block, an IllegalStateException is thrown, with the caught exception as its cause.

  6. After the try-catch block, a finally block is used to ensure that the reader is properly closed. It is closed by calling the close() method on the reader. If an IOException occurs while closing the reader, an IllegalStateException is thrown, with the caught exception as its cause.

  7. Finally, the method creates a new CharArrayCharSource object by passing the contents of stringBuilder as a String to its constructor.

  8. The method returns the created CharSource object.

This method basically reads the contents of a Reader object line by line, appends each line to a StringBuilder, and converts the resulting string into a CharSource object. sequence diagram

CharArrayCharSource

This class is a char source for char arrays. It is used by the parser to parse strings and is not thread safe. It is not a general-purpose char source.

public static String debugCharDescription(int c)

public static String debugCharDescription(int c) {
    String charString;
    if (c == ' ') {
        charString = "[SPACE]";
    } else if (c == '\t') {
        charString = "[TAB]";
    } else if (c == '\n') {
        charString = "[NEWLINE]";
    } else if (c == ETX) {
        charString = "ETX";
    } else {
        charString = "'" + (char) c + "'";
    }
    charString = charString + " with an int value of " + c;
    return charString;
}

Method Description: debugCharDescription

This method is defined in the io.nats.jparse.source.CharArrayCharSource class. Its purpose is to return a debug description of a given character by converting it to a string representation.

Parameters:

  • c (int): The input character to be debugged.

Return Value:

  • The method returns a string representation of the input character, along with additional debug information.

Step-by-Step Description:

  1. Initialize the variable charString to store the debug description of the character.
  2. Check if the input character c is equal to a space character (' ').
  3. If true, assign "[SPACE]" to charString.
  4. If the character is not a space, check if it is a tab character (' ').
  5. If true, assign "[TAB]" to charString.
  6. If the character is not a tab, check if it is a newline character ('\n').
  7. If true, assign "[NEWLINE]" to charString.
  8. If the character is not a newline, check if it is equal to a specific constant ETX.
  9. If true, assign "ETX" to charString.
  10. If the character does not match any of the previous conditions, it is considered a regular character.
  11. Assign the character surrounded by single quotes (') to charString.
  12. Concatenate the string " with an int value of " and the value of c to charString.
  13. Return the value of charString as the debug description of the character. sequence diagram

@Override

public int next()

@Override
public int next() {
    if (index + 1 >= data.length) {
        index = data.length;
        return ETX;
    }
    return data[++index];
}

The next() method in the io.nats.jparse.source.CharArrayCharSource class is used to retrieve the next character from the source data.

Here is a step-by-step description of what this method does based on its body:

  1. Check if the current index (index) plus 1 is equal to or greater than the length of the data array.
  2. If the condition is true, it means that there are no more characters to read from the source data. 2.1. Set the index to the length of the data array to indicate that we have reached the end. 2.2. Return the ETX constant, which represents the end-of-transmission character.
  3. If the condition in step 1 is false, it means that there are more characters to read from the source data. 3.1. Increment the index by 1. 3.2. Return the character at the new index in the data array.

This method basically keeps track of the current position in the data array and returns the next character each time it is called. If there are no more characters to read, it returns the ETX constant to indicate the end of transmission. sequence diagram

@Override

public void checkForJunk()

@Override
public void checkForJunk() {
    int index = this.index;
    final char[] data = this.data;
    final int length = data.length;
    int ch = ETX;
    for (; index < length; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                throw new UnexpectedCharacterException("Junk", "Unexpected extra characters", this);
        }
    }
}

The checkForJunk method, defined in the CharArrayCharSource class, is used to check if there are any unexpected extra characters in the data stored in the CharArrayCharSource object.

Here is a step-by-step description of what the method does based on its body:

  1. It begins by initializing the index variable with the current index of the character being checked in the data.
  2. It then retrieves the data stored in the CharArrayCharSource object and assigns it to the data variable.
  3. The length of the data is obtained and stored in the length variable.
  4. The ch variable is initialized with the ETX (End of Text) character. ETX typically has a value of 3.
  5. A loop is initiated where index is incremented for every iteration until it reaches the length of the data.
  6. Inside the loop, the character at the current index in the data is assigned to the ch variable.
  7. The switch statement is used to perform different actions based on the value of ch.
  8. If ch matches any of the four white space characters - NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, or SPACE_WS, the loop continues to the next iteration without executing any further code.
  9. If ch does not match any of the white space characters, it means that an unexpected character is found.
  10. In such a case, an UnexpectedCharacterException is thrown with the message "Junk" and "Unexpected extra characters", along with the current CharArrayCharSource object as an argument.
  11. If the loop completes without throwing an exception, it means that there are no unexpected extra characters in the data. sequence diagram

@Override

public int nextSkipWhiteSpace()

@Override
public int nextSkipWhiteSpace() {
    int index = this.index + 1;
    final char[] data = this.data;
    final int length = data.length;
    int ch = ETX;
    loop: for (; index < length; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                break loop;
        }
    }
    this.index = index;
    return index == length ? ETX : ch;
}

The method nextSkipWhiteSpace is defined in the class io.nats.jparse.source.CharArrayCharSource. Here is a step-by-step description of what this method does based on its body:

  1. It overrides the method nextSkipWhiteSpace from the superclass.

  2. It initializes a variable index with the value of this.index + 1.

  3. It retrieves the character array data and its length from the class.

  4. It initializes a variable ch with the value of ETX (end-of-text character).

  5. It starts a loop that iterates over the characters in the data array, starting from the index value.

  6. Inside the loop, it assigns the current character to the variable ch.

  7. It uses a switch statement to check the value of ch against four possible whitespace characters: NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, and SPACE_WS.

  8. If ch matches any of the whitespace characters, the loop continues to the next iteration without executing the remaining code in the loop body.

  9. If ch does not match any of the whitespace characters, the loop is exited using the break statement with the loop label.

  10. After the loop, the variable index is assigned the value of the loop index.

  11. The method then returns the value of ch, unless index is equals to length, in which case it returns the value of ETX.

Please note that the values ETX, NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, and SPACE_WS are not defined in the given code snippet and their meanings may be specific to the context of the code. sequence diagram

@Override

public char skipWhiteSpace()

@Override
public char skipWhiteSpace() {
    int index = this.index;
    final char[] data = this.data;
    final int length = data.length;
    char ch;
    loop: for (; index < length; index++) {
        ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                break loop;
        }
    }
    this.index = index;
    return data[index];
}

The skipWhiteSpace method in the CharArrayCharSource class is used to skip any white space characters (including new lines, carriage returns, tabs, and spaces) in the character array data.

Here is a step-by-step description of what the method does:

  1. It starts by getting the current index from the this.index variable and assigning it to a local variable index.
  2. It also assigns the character array data from the this.data variable to a local variable data.
  3. It gets the length of the character array and assigns it to a local variable length.
  4. It declares a local variable ch to hold the current character.
  5. It starts a loop that iterates over the character array from the current index index up to the length of the character array length.
  6. Inside the loop, it assigns the current character at the current index index to the variable ch.
  7. It uses a switch statement to check if the ch is a white space character (defined as NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, or SPACE_WS).
  8. If the ch is a white space character, it continues to the next iteration of the loop.
  9. If the ch is not a white space character, it breaks the loop using a break loop statement.
  10. After the loop finishes, it updates the index this.index to the value of the local variable index.
  11. Finally, it returns the character ch found at the index index in the character array data.

In summary, the skipWhiteSpace method iterates over a character array, starting from the current index, and skips any white space characters until it finds a non-white space character. It then updates the index and returns the non-white space character found. sequence diagram

@Override

public String toEncodedStringIfNeeded(int start, int end)

@Override
public String toEncodedStringIfNeeded(int start, int end) {
    if (CharArrayUtils.hasEscapeChar(data, start, end)) {
        return getEncodedString(start, end);
    } else {
        return this.getString(start, end);
    }
}

The toEncodedStringIfNeeded method defined in the CharArrayCharSource class in the io.nats.jparse.source package is responsible for converting a portion of the character array to a string, checking if any escape characters exist within the range.

Here is a step-by-step description of what the method does based on its body:

  1. The method overrides the toEncodedStringIfNeeded method from its parent class.
  2. It takes two parameters, start and end, which represent the start and end indices of the desired portion of the character array to convert.
  3. The method starts by checking whether the character array within the specified range contains any escape characters using the CharArrayUtils.hasEscapeChar method.
  4. If any escape characters are found, it invokes the getEncodedString method with the same start and end indices to return the properly encoded string.
  5. If no escape characters are found within the specified range, it invokes the getString method with the same start and end indices to return the string as it is without any encoding.
  6. The method then returns the result of either the encoded or raw string based on the presence of escape characters within the specified range. sequence diagram

@Override

public NumberParseResult findEndOfNumberFast()

@Override
public NumberParseResult findEndOfNumberFast() {
    int i = index + 1;
    char ch = 0;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i, false);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case DECIMAL_POINT:
                index = i;
                return findEndOfFloatFast();
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponentFast();
            default:
                throw new IllegalStateException("Unexpected character " + ch + " at index " + index);
        }
    }
    index = i;
    return new NumberParseResult(i, false);
}

Method: findEndOfNumberFast

This method is defined in the class io.nats.jparse.source.CharArrayCharSource and is used to find the end of a number in a character array.

Step 1:

Initialize the variable i to index + 1.

Step 2:

Initialize the variable ch to 0.

Step 3:

Get a reference to the character array data from this.data.

Step 4:

Get the length of the character array and store it in the variable length.

Step 5:

Start a for loop, iterating from i to length.

Step 6:

Inside the loop, get the character at index i and store it in the variable ch.

Step 7:

Use a switch statement to check for various characters:

  • If ch is a whitespace character (e.g. newline, carriage return, tab, space) or one of the specified tokens (ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, ARRAY_END_TOKEN), update the value of index to i and return a new NumberParseResult with the updated index and false as the boolean value.
  • If ch is any of the digits (NUM_0, NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, NUM_9), continue to the next iteration.
  • If ch is a decimal point (DECIMAL_POINT), update the value of index to i and return the result of calling the method findEndOfFloatFast().
  • If ch is an exponent marker (EXPONENT_MARKER or EXPONENT_MARKER2), update the value of index to i and return the result of calling the method parseFloatWithExponentFast().
  • If none of the above conditions match, throw an IllegalStateException with the message "Unexpected character" followed by ch and the current value of index.

Step 8:

After the loop ends, update the value of index to i.

Step 9:

Finally, return a new NumberParseResult with i as the index and false as the boolean value. sequence diagram

private NumberParseResult findEndOfFloatFast()

private NumberParseResult findEndOfFloatFast() {
    int i = index + 1;
    char ch = 0;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponentFast();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Float Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i, true);
}

The method findEndOfFloatFast in class io.nats.jparse.source.CharArrayCharSource is used to find the end of a floating-point number in a character array. It returns a NumberParseResult object which contains the position of the end of the floating-point number and a flag indicating if the parsing was successful.

Here is a step-by-step description of what the method does:

  1. Initialize a local variable i to index + 1, where index is an instance variable representing the current position in the character array.
  2. Initialize a local variable ch to 0.
  3. Get a reference to the character array data from the this object, which refers to the current instance of CharArrayCharSource.
  4. Get the length of the character array and store it in a local variable length.
  5. Start a for loop, iterating over the characters in the character array starting from i and going up to length - 1.
  6. Inside the loop, get the current character at index i and store it in the variable ch.
  7. Use a switch statement to check the value of ch against various cases:
    • If ch is equal to any of the whitespace characters (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS) or any of the JSON delimiters (ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, ARRAY_END_TOKEN), then update the index variable to i and return a new NumberParseResult object with the current position i and true as the flag.
    • If ch is any of the digit characters (NUM_0 to NUM_9), continue to the next iteration of the loop.
    • If ch is either the exponent marker character (EXPONENT_MARKER) or the second exponent marker character (EXPONENT_MARKER2), update the index variable to i and return the result of calling the parseFloatWithExponentFast method.
    • If none of the above cases match, throw an UnexpectedCharacterException with the message "Parsing JSON Float Number", "Unexpected character", the current instance of CharArrayCharSource, the unexpected character ch, and the current position i.
  8. After the for loop finishes, update the index variable to i and return a new NumberParseResult object with the current position i and true as the flag. sequence diagram

private NumberParseResult parseFloatWithExponentFast()

private NumberParseResult parseFloatWithExponentFast() {
    int i = index + 1;
    char ch = 0;
    int signOperator = 0;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i, true);
            case MINUS:
            case PLUS:
                signOperator++;
                if (signOperator > 1) {
                    throw new IllegalStateException("Too many sign operators when parsing exponent of float");
                }
                break;
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            default:
                throw new IllegalStateException("Unexpected character " + ch + " at index " + index);
        }
    }
    index = i;
    return new NumberParseResult(i, true);
}

The method parseFloatWithExponentFast is defined in the class io.nats.jparse.source.CharArrayCharSource and returns a NumberParseResult.

Here is a step by step description of what the method is doing:

  1. Initialize the variable i with the value index + 1.
  2. Initialize the variable ch with the value 0.
  3. Initialize the variable signOperator with the value 0.
  4. Get the character array data from the object this and assign it to the variable data.
  5. Get the length of the character array data and assign it to the variable length.
  6. Start a loop that iterates from the value of i to the length of data.
    • Assign the current character of data at index i to the variable ch.
    • Use a switch statement to check the value of ch against various cases:
      • If ch is equal to any of the following characters: NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, ATTRIBUTE_SEP, ARRAY_SEP, OBJECT_END_TOKEN, or ARRAY_END_TOKEN, set index to i and return a new NumberParseResult object with the value of i and true.
      • If ch is equal to MINUS or PLUS, increment signOperator by 1. If signOperator is greater than 1, throw an IllegalStateException with the message "Too many sign operators when parsing exponent of float".
      • If ch is any of the digits from NUM_0 to NUM_9, continue to the next iteration.
      • If none of the above cases match, throw an IllegalStateException with the message "Unexpected character" followed by the value of ch and "at index" followed by the value of index.
  7. After the loop ends, set index to i and return a new NumberParseResult object with the value of i and true. sequence diagram

@Override

public int findEndOfEncodedStringFast()

@Override
public int findEndOfEncodedStringFast() {
    int i = ++index;
    final char[] data = this.data;
    final int length = data.length;
    boolean controlChar = false;
    for (; i < length; i++) {
        char ch = data[i];
        switch(ch) {
            case CONTROL_ESCAPE_TOKEN:
                controlChar = !controlChar;
                continue;
            case STRING_END_TOKEN:
                if (!controlChar) {
                    index = i + 1;
                    return i;
                }
                controlChar = false;
                break;
            default:
                controlChar = false;
                break;
        }
    }
    throw new IllegalStateException("Unable to find closing for String");
}

The findEndOfEncodedStringFast method in the io.nats.jparse.source.CharArrayCharSource class is used to find the index of the closing delimiter of an encoded string. Here is a step-by-step description of what this method does:

  1. Initialize i with the incremented index value. This will be the starting index for searching the encoding string.
  2. Get the character array data from the source object.
  3. Get the length of the character array.
  4. Initialize a boolean variable controlChar to false. This variable is used to keep track of whether a control character was encountered.
  5. Start a loop from i to length - 1. This loop iterates over each character in the character array.
  6. Get the character ch at index i.
  7. Check the value of ch using a switch statement to handle different cases:
    • If ch is equal to the CONTROL_ESCAPE_TOKEN, toggle the value of controlChar (i.e., change it from true to false or vice versa).
    • If ch is equal to the STRING_END_TOKEN and controlChar is false, update the index to i + 1 and return i. This denotes that the end of the encoding string has been found.
    • If ch is anything else, set controlChar to false.
  8. If no end of the encoding string is found, throw an IllegalStateException with the message "Unable to find closing for String".

Overall, this method iterates over the characters in the input character array starting from the given index and looks for the closing delimiter of an encoded string. It handles control characters and ensures that the closing delimiter is not escaped before returning the index of the closing delimiter. If the closing delimiter is not found, it throws an exception to indicate the error. sequence diagram

private int findEndOfStringControlEncode(int i)

private int findEndOfStringControlEncode(int i) {
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    ch = data[i];
    switch(ch) {
        case CONTROL_ESCAPE_TOKEN:
        case STRING_END_TOKEN:
        case 'n':
        case 'b':
        case '/':
        case 'r':
        case 't':
        case 'f':
            return i;
        case 'u':
            return findEndOfHexEncoding(i);
        default:
            throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
    }
}

Method: findEndOfStringControlEncode in io.nats.jparse.source.CharArrayCharSource

This method is used to find the index of the closing character for a JSON string when the starting character is a control or escape character.

Parameters:

  • i: Index of the starting character in the data array.

Steps:

  1. Get the reference to the data array and the length of the array.
  2. Initialize a char variable ch to 0.
  3. Assign the value of the character at index i in the data array to the ch variable.
  4. Perform a switch-case check on the value of ch:
    • If ch is equal to CONTROL_ESCAPE_TOKEN, STRING_END_TOKEN, 'n', 'b', '/', 'r', 't', or 'f', return the current index i.
    • If ch is equal to 'u', call the findEndOfHexEncoding method and return its result.
    • If ch does not match any of the above cases, throw an UnexpectedCharacterException with appropriate error message, indicating an unexpected character while finding the closing character for the JSON string.

Return:

  • The index of the closing character for the JSON string.

Note: The actual values for CONTROL_ESCAPE_TOKEN and STRING_END_TOKEN are not provided in the given code snippet sequence diagram

@Override

public int findEndOfEncodedString()

@Override
public int findEndOfEncodedString() {
    int i = ++index;
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case CONTROL_ESCAPE_TOKEN:
                i = findEndOfStringControlEncode(i + 1);
                continue;
            case STRING_END_TOKEN:
                index = i + 1;
                return i;
            default:
                if (ch >= SPACE_WS) {
                    continue;
                }
                throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing JSON Encoded String", "Unable to find closing for String", this, ch, i);
}

The findEndOfEncodedString method is defined in the io.nats.jparse.source.CharArrayCharSource class. It overrides the findEndOfEncodedString method from its parent class.

Here is a step-by-step description of what the method is doing:

  1. The index variable is incremented by 1 and stored in the i variable.
  2. The data char array from the parent class is assigned to the data variable.
  3. The length of the data array is stored in the length variable.
  4. A variable ch is initialized to the value 0.
  5. A for loop is started, iterating from i to length.
  6. In each iteration, the value at the current index i in the data array is stored in the ch variable.
  7. A switch statement is used to check the value of ch.
  8. If ch is equal to CONTROL_ESCAPE_TOKEN, then the findEndOfStringControlEncode method is called with the argument i + 1. The returned value is then assigned back to i, and the next iteration of the loop is started.
  9. If ch is equal to STRING_END_TOKEN, then index is updated to i + 1, and the current value of i is returned.
  10. If ch is none of the above, it checks if ch is greater than or equal to SPACE_WS.
    • If it is, the loop continues to the next iteration.
    • If it is not, an UnexpectedCharacterException is thrown with a message stating the unexpected character, the current CharSource, ch, and the index i.
  11. If the loop completes without finding a closing for a string, an UnexpectedCharacterException is thrown with a message stating the inability to find a closing for the string, the current CharSource, the last value of ch, and the last value of i.

This method is used to find the end of an encoded string in the JSON input. It iterates through the characters starting from the index position and looks for the closing token of the string or any control escape tokens. If a closing token is found, it updates the index and returns the position of the closing token. If a control escape token is found, it delegates the further handling to the findEndOfStringControlEncode method. If none of these conditions are met, it throws an exception indicating an unexpected character. If the loop completes without finding a closing token, it throws an exception indicating the inability to find a closing for the string. sequence diagram

private int findEndOfHexEncoding(int index)

private int findEndOfHexEncoding(int index) {
    final char[] data = this.data;
    final int length = data.length;
    if (isHex(data[++index]) && isHex(data[++index]) && isHex(data[++index]) && isHex(data[++index])) {
        return index;
    } else {
        throw new UnexpectedCharacterException("Parsing hex encoding in a string", "Unexpected character", this);
    }
}

The method findEndOfHexEncoding in the class io.nats.jparse.source.CharArrayCharSource is used to find the end of a hex encoding in a string. Here is a step-by-step description of what the method is doing:

  1. It takes an input parameter index which represents the current position in the character array.

  2. It initializes a local variable data with the character array this.data and length with the length of the character array.

  3. It checks if the character at position index + 1, index + 2, index + 3, and index + 4 in the data array are valid hexadecimal characters by calling the method isHex().

  4. If all four characters are valid hex characters, the method returns the current index value.

  5. If any of the characters is not a valid hex character, the method throws an exception of type UnexpectedCharacterException with the error message "Unexpected character" and a reference to the this object.

In summary, the findEndOfHexEncoding method iterates over the characters in the data array starting from the index position and checks if the next four characters form a valid hex encoding. If they do, it returns the index of the last character in the encoding. If any of the characters is not valid, it throws an exception. sequence diagram

private boolean isHex(char datum)

private boolean isHex(char datum) {
    switch(datum) {
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            return true;
        default:
            return false;
    }
}

The method isHex in the class io.nats.jparse.source.CharArrayCharSource is used to check if a given character is a hex digit.

Here is the step-by-step description of what the method is doing based on its body:

  1. Define a private boolean method named isHex that takes a character as input.
  2. The method uses a switch-case statement to handle different cases of the datum character.
  3. In the switch statement, each case represents a valid hex digit character. The valid hex digit characters are: 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', and '9'.
  4. If the datum character matches any of the valid hex digit characters, the method returns true.
  5. If the datum character does not match any of the valid hex digit characters, the method returns false.
  6. The method is private, which means it can only be accessed within the io.nats.jparse.source.CharArrayCharSource class.

That's the step-by-step description of the isHex method. It checks whether a given character is a valid hex digit character. sequence diagram

@Override

public int findAttributeEnd()

@Override
public int findAttributeEnd() {
    int index = this.index;
    final char[] data = this.data;
    final int length = this.data.length;
    loop: for (; index < length; index++) {
        char ch = data[index];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
                this.index = index;
                break loop;
        }
    }
    return index;
}

The method findAttributeEnd in the class io.nats.jparse.source.CharArrayCharSource is used to find the end of an attribute within a character array.

Here are the steps performed by this method:

  1. The method overrides the findAttributeEnd method, ensuring that it has the same signature as the overridden method in the parent class.

  2. The method first initializes a local variable index with the value of this.index. this.index refers to the current index within the character array.

  3. The method then assigns the character array data from the class instance to a local variable data.

  4. It also assigns the length of the character array data to a local variable length.

  5. The method has a loop that iterates from the current index index to the length of the character array data.

  6. Inside the loop, it retrieves the character ch at the current index index from the character array data.

  7. It then checks the character ch against several predefined whitespace characters, like NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS, and ATTRIBUTE_SEP.

  8. If the character ch matches any of these whitespace characters, it updates the this.index to the current index index and breaks the loop using a labeled break statement.

  9. After the loop ends, the method returns the value of index.

To summarize, the findAttributeEnd method scans through the character array data starting from the current index this.index until it finds a whitespace character. It then returns the index where the whitespace character was found, indicating the end of the attribute. sequence diagram

@Override

public boolean findChar(char c)

@Override
public boolean findChar(char c) {
    int index = this.index;
    final char[] data = this.data;
    final int length = this.data.length;
    for (; index < length; index++) {
        if (data[index] == c) {
            this.index = index;
            return true;
        }
    }
    return false;
}

The findChar method in the CharArrayCharSource class is used to find a specific character in a character array.

Here is a step-by-step description of what the method is doing:

  1. The method overrides the findChar method declared in the superinterface or superclass.

  2. The this.index variable is assigned to a local variable called index. This is done for efficiency purposes as retrieving the instance variable directly multiple times may be costly.

  3. The this.data variable, which represents the character array being searched, is assigned to a local variable called data.

  4. The length variable is assigned the length of the data character array.

  5. A for loop is started with index being the starting point of the loop and length being the end condition.

  6. Inside the loop, the current character at the index position of the data array is compared to the character c that is being searched for.

  7. If the characters match, the this.index instance variable is updated with the current index value, and the method returns true indicating that the character was found.

  8. If the loop completes without finding a matching character, the method returns false indicating that the character was not found in the array.

Please note that this step-by-step description assumes that the instance variables (this.index, this.data, etc.) are properly initialized before using the findChar method. sequence diagram

@Override

public int findEndString()

@Override
public int findEndString() {
    int i = ++index;
    final char[] data = this.data;
    final int length = data.length;
    char ch = 0;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case STRING_END_TOKEN:
                index = i;
                return i;
            default:
                if (ch >= SPACE_WS) {
                    continue;
                }
                throw new UnexpectedCharacterException("Parsing JSON String", "Unexpected character while finding closing for String", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing JSON String", "Unable to find closing for String", this, ch, i);
}

The findEndString method in the CharArrayCharSource class is used to find the end of a JSON string within a given character array.

Here is a step-by-step description of what the method is doing:

  1. Increment the index by 1 and assign it to a local variable i.
  2. Retrieve the character array data from the current CharArrayCharSource object.
  3. Get the length of the character array and assign it to a local variable length.
  4. Initialize a variable ch with a value of 0.
  5. Start a loop from the current index (i) until the end of the character array.
  6. Within the loop, retrieve the character at position i from the character array and assign it to ch.
  7. Use a switch statement to check the value of ch.
    • If ch is equal to a constant variable STRING_END_TOKEN, update the index with the current value of i and return i as the index of the end of the string.
    • If ch is not equal to STRING_END_TOKEN, check if ch is greater than or equal to a constant variable SPACE_WS (a whitespace character).
      • If it is greater than or equal to SPACE_WS, continue to the next iteration of the loop.
      • If it is not greater than or equal to SPACE_WS, throw an UnexpectedCharacterException with a message indicating that an unexpected character was found while finding the closing for the string.
  8. If the end of the character array is reached without finding the closing for the string, throw an UnexpectedCharacterException with a message indicating that the closing for the string was not found.

In summary, this method iterates over the character array starting from the current index, looking for the end of a JSON string. If the end of the string is found, the method returns the index of the closing character. If an unexpected character is encountered, an exception is thrown. If the closing for the string is not found, an exception is also thrown. sequence diagram

@Override

public NumberParseResult findEndOfNumber()

@Override
public NumberParseResult findEndOfNumber() {
    final char startCh = getCurrentChar();
    final int startIndex = index;
    char ch = startCh;
    int i = index + 1;
    final char[] data = this.data;
    final int length = data.length;
    loop: for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                break loop;
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case DECIMAL_POINT:
                if (startCh == MINUS) {
                    final int numLenSoFar = i - startIndex;
                    if (numLenSoFar == 1) {
                        throw new UnexpectedCharacterException("Parsing JSON Number", "Unexpected character", this, ch, i);
                    }
                }
                index = i;
                return findEndOfFloat();
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponent();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    final int numLength = i - startIndex;
    switch(startCh) {
        case NUM_0:
            if (numLength != 1) {
                throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't start with a 0 ", this, startCh, startIndex);
            }
            break;
        case PLUS:
            throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't start with a plus ", this, startCh, startIndex);
        case MINUS:
            switch(numLength) {
                case 1:
                    throw new UnexpectedCharacterException("Parsing JSON Int Number", "Int can't be only a minus, number is missing", this, startCh, startIndex);
                case 2:
                    break;
                default:
                    if (data[startIndex + 1] == NUM_0) {
                        throw new UnexpectedCharacterException("Parsing JSON Int Number", "0 can't be after minus sign", this, startCh, startIndex);
                    }
            }
    }
    return new NumberParseResult(i, false);
}

The findEndOfNumber method is defined in the io.nats.jparse.source.CharArrayCharSource class. This method is used to find the end index of a number in a character array source.

Here is a step-by-step description of what the method is doing:

  1. It starts by getting the current character from the character array source (getCurrentChar()).
  2. It initializes the startCh variable with the current character and the startIndex variable with the current index.
  3. It then enters a loop starting from i = index + 1 until i < length (where length is the length of the character array).
  4. Inside the loop, it assigns the next character (data[i]) to the ch variable.
  5. It then performs a switch case on the ch variable to check for various characters and perform different actions:
    • If the character is one of the whitespace characters (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS), one of the separators (ATTRIBUTE_SEP, ARRAY_SEP), or one of the end tokens (OBJECT_END_TOKEN, ARRAY_END_TOKEN), it breaks out of the loop.
    • If the character is one of the digits (NUM_0 to NUM_9), it continues to the next iteration of the loop.
    • If the character is a decimal point (DECIMAL_POINT), it checks if the startCh is a minus sign (MINUS). If it is, it throws an exception if the length of the number so far is 1. It then sets the index to i and returns the result of calling the findEndOfFloat method.
    • If the character is an exponent marker (EXPONENT_MARKER or EXPONENT_MARKER2), it sets the index to i and returns the result of calling the parseFloatWithExponent method.
    • If none of the above cases match, it throws an exception for an unexpected character.
  6. After the loop, it sets the index to i (the current index) and calculates the length of the number (numLength) by subtracting the startIndex from i.
  7. It then performs a switch case on the startCh variable to check for different cases:
    • If the startCh is NUM_0 and the length of the number is not 1, it throws an exception for an integer number starting with 0.
    • If the startCh is a plus sign (PLUS), it throws an exception for an integer number starting with a plus sign.
    • If the startCh is a minus sign (MINUS), it checks the length of the number:
      • If the length is 1, it throws an exception for a minus sign without a number.
      • If the length is 2, it continues to the next step.
      • If the length is greater than 2 and the character after the minus sign is NUM_0, it throws an exception for 0 being after a minus sign.
  8. Finally, it returns a new NumberParseResult object with the end index (i) and a boolean indicating whether the number is a float (false). sequence diagram

private NumberParseResult findEndOfFloat()

private NumberParseResult findEndOfFloat() {
    int i = index + 1;
    char ch = (char) next();
    if (!isNumber(ch)) {
        throw new UnexpectedCharacterException("Parsing float part of number", "After decimal point expecting number but got", this, ch, this.index);
    }
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            case EXPONENT_MARKER:
            case EXPONENT_MARKER2:
                index = i;
                return parseFloatWithExponent();
            default:
                throw new UnexpectedCharacterException("Parsing JSON Float Number", "Unexpected character", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i, true);
}

The findEndOfFloat method is a private method defined in the io.nats.jparse.source.CharArrayCharSource class. It is responsible for finding the end of a floating-point number within a JSON string.

Here is a step-by-step description of what the method does based on its body:

  1. The method starts by initializing a local variable i with the value of index + 1.

  2. It then retrieves the next character ch from the input stream by calling the next() method.

  3. It checks if ch is a valid number character by calling the isNumber() method. If it is not a valid number character, it throws an UnexpectedCharacterException with an appropriate error message.

  4. Next, the method accesses the data array and length of the array from the parent object.

  5. It enters a for loop that iterates from i to length-1.

  6. Inside the loop, it gets the character ch at the current index i from the data array.

  7. It then performs a switch statement on ch to handle different types of characters:

    • If ch is one of the whitespace characters or special tokens (e.g., newline, tab, comma, array or object end token), it sets the index to i and returns a new NumberParseResult object with the current index and true value indicating that the float number has been successfully parsed.

    • If ch is one of the numeric digits (0-9), it continues the loop without performing any action.

    • If ch is an exponent marker (e.g., 'E' or 'e'), it sets the index to i and calls the parseFloatWithExponent method to handle parsing of the floating-point number with an exponent.

    • If ch is any other character, it throws an UnexpectedCharacterException with an appropriate error message.

  8. After the loop, it sets the index to i and returns a new NumberParseResult object with the current index and true value indicating that the float number has been successfully parsed.

In summary, the findEndOfFloat method iterates through the characters of the input string starting from the index + 1 position. It checks each character to determine if it is a valid part of a floating-point number, until it reaches a character that is not part of the number. It returns the index of the last character of the number and whether or not the number was successfully parsed. sequence diagram

private boolean isNumber(final char ch)

private boolean isNumber(final char ch) {
    switch(ch) {
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
            return true;
        default:
            return false;
    }
}

Method Description: isNumber

Class: io.nats.jparse.source.CharArrayCharSource

The isNumber method is defined within the io.nats.jparse.source.CharArrayCharSource class. It determines whether a given character is a number or not.

Parameter: ch

  • Type: char
  • Description: The character that needs to be checked.

Steps:

  1. The method takes a character ch as input.
  2. It uses a switch statement to compare the input character with a set of predefined constants representing the digits 0 to 9.
  3. If the input character matches any of the predefined constants, the method returns true, indicating that the character is a number.
  4. If the input character does not match any of the predefined constants, the method returns false, indicating that the character is not a number. sequence diagram

private NumberParseResult parseFloatWithExponent()

private NumberParseResult parseFloatWithExponent() {
    char ch = (char) next();
    if (!isNumberOrSign(ch)) {
        throw new UnexpectedCharacterException("Parsing exponent part of float", "After exponent expecting number or sign but got", this, ch, this.index);
    }
    if (isSign(ch)) {
        ch = (char) next();
        if (!isNumber(ch)) {
            throw new UnexpectedCharacterException("Parsing exponent part of float after sign", "After sign expecting number but got", this, ch, this.index);
        }
    }
    int i = index + 1;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
            case ATTRIBUTE_SEP:
            case ARRAY_SEP:
            case OBJECT_END_TOKEN:
            case ARRAY_END_TOKEN:
                index = i;
                return new NumberParseResult(i, true);
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
                break;
            default:
                throw new UnexpectedCharacterException("Parsing Float with exponent", "Unable to find closing for Number", this, ch, i);
        }
    }
    index = i;
    return new NumberParseResult(i, true);
}

The method parseFloatWithExponent() in class io.nats.jparse.source.CharArrayCharSource is used to parse a floating-point number with an exponent.

Here is a step-by-step description of what the method does based on its body:

  1. It retrieves the next character from the input and assigns it to the variable ch.
  2. It checks if the character ch is a number or a sign. If it is not, it throws an UnexpectedCharacterException with an error message indicating that it was expecting a number or sign.
  3. If the character ch is a sign, it retrieves the next character from the input and assigns it to ch. Then, it checks if ch is a number. If it is not, it throws an UnexpectedCharacterException with an error message indicating that it was expecting a number after the sign.
  4. It initializes an integer variable i with index + 1. This will be used to iterate through the characters in the data array.
  5. It retrieves the data array and its length and assigns them to data and length variables respectively.
  6. It enters a loop that starts at i and iterates until i reaches the end of the data array.
  7. Inside the loop, it assigns the current character at index i to ch.
  8. It then checks the value of ch against a set of characters representing whitespace, separators, and the end of objects/arrays. If a match is found, it updates the value of index to i and returns a new NumberParseResult object with the current index and a flag indicating successful parsing.
  9. If ch is a digit (0-9), it continues to the next iteration of the loop.
  10. If ch does not match any of the previous cases, it throws an UnexpectedCharacterException with an error message indicating that it was unable to find a closing character for the number.
  11. After the loop ends, it updates the value of index to i and returns a new NumberParseResult object with the current index and a flag indicating successful parsing. sequence diagram

private boolean isNumberOrSign(char ch)

private boolean isNumberOrSign(char ch) {
    switch(ch) {
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            return true;
        default:
            return false;
    }
}

Method: isNumberOrSign

This method is defined in the io.nats.jparse.source.CharArrayCharSource class and is used to determine if a given character is either a number or a sign (+ or -).

Algorithm:

  1. Accept a character ch as input.
  2. Perform a switch case on the character ch.
  3. Check if ch matches any of the following cases:
    • If ch is equal to the character constant NUM_0
    • If ch is equal to the character constant NUM_1
    • If ch is equal to the character constant NUM_2
    • If ch is equal to the character constant NUM_3
    • If ch is equal to the character constant NUM_4
    • If ch is equal to the character constant NUM_5
    • If ch is equal to the character constant NUM_6
    • If ch is equal to the character constant NUM_7
    • If ch is equal to the character constant NUM_8
    • If ch is equal to the character constant NUM_9
    • If ch is equal to the character constant MINUS
    • If ch is equal to the character constant PLUS
  4. If ch matches any of the above cases, return true.
  5. If ch does not match any of the above cases, return false. sequence diagram

private boolean isSign(char ch)

private boolean isSign(char ch) {
    switch(ch) {
        case MINUS:
        case PLUS:
            return true;
        default:
            return false;
    }
}

The isSign method in the CharArrayCharSource class is used to determine if a given character is a sign symbol.

Here is a step-by-step description of what the method does:

  1. The method takes a single character (ch) as input.
  2. It uses a switch statement to check the value of ch.
  3. If the value of ch is equal to the constant MINUS, which represents a minus sign, or the constant PLUS, which represents a plus sign, it returns true.
  4. If the value of ch does not match any of the cases in the switch statement, it returns false.

In summary, the isSign method checks if a given character is a sign symbol (minus or plus) and returns true if it is or false if it isn't. sequence diagram

@Override

public int findFalseEnd()

@Override
public int findFalseEnd() {
    if (this.data[++index] == 'a' && this.data[++index] == 'l' && this.data[++index] == 's' && this.data[++index] == 'e') {
        return ++index;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON False Boolean", "Unexpected character", this);
    }
}

The findFalseEnd method defined in the CharArrayCharSource class, located at io.nats.jparse.source.CharArrayCharSource, performs the following steps:

  1. The method overrides the findFalseEnd method, which is declared in a superclass.
  2. It starts by checking if the character at the next index (++index) in the data array is equal to the character 'a'.
  3. If the above condition is true, it also checks if the character at the next index is 'l', 's', and 'e' respectively.
  4. If all the characters 'a', 'l', 's', and 'e' are found consecutively, it returns the incremented index value.
  5. If any of the characters are not found consecutively, it throws an UnexpectedCharacterException.
  6. The UnexpectedCharacterException takes the following parameters: "Parsing JSON False Boolean" as the error message, "Unexpected character" as the detailed error description, and the current CharArrayCharSource instance (this) where the exception occurred.

Please note that the ++index operator increments the index value and returns the incremented value. sequence diagram

@Override

public int findTrueEnd()

@Override
public int findTrueEnd() {
    if (this.data[++index] == 'r' && this.data[++index] == 'u' && this.data[++index] == 'e') {
        return ++index;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON True Boolean", "Unexpected character", this);
    }
}

The findTrueEnd method in the io.nats.jparse.source.CharArrayCharSource class is used to determine the end position of the "true" boolean value in a JSON document.

Here is a step-by-step breakdown of how the method works:

  1. The method overrides the findTrueEnd method defined in the superclass.
  2. The method begins by incrementing the index variable using the ++index syntax.
  3. It then checks if the character at the current index in the data array is equal to the character 'r'.
  4. If it is, the method proceeds to the next step.
  5. The method then increments the index variable again and checks if the character at the new index is equal to the character 'u'.
  6. If it is, the method proceeds to the next step.
  7. The method increments the index variable once more and checks if the character at the new index is equal to the character 'e'.
  8. If it is, the method increments the index again and returns the new index value.
  9. If any of the checks made in steps 3, 5, or 7 fail, it means that there is an unexpected character in the input, and the method throws an UnexpectedCharacterException.
  10. The UnexpectedCharacterException is constructed with a message indicating that it occurred while parsing a JSON True Boolean and providing the information about the unexpected character and the current source.
  11. The exception is thrown, and the execution of the method is stopped.

This findTrueEnd method allows the caller to locate the true boolean value in a JSON document and determine its end position. sequence diagram

@Override

public boolean findObjectEndOrAttributeSep()

@Override
public boolean findObjectEndOrAttributeSep() {
    int i = index;
    char ch = 0;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case OBJECT_END_TOKEN:
                this.index = i + 1;
                return true;
            case ATTRIBUTE_SEP:
                this.index = i;
                return false;
        }
    }
    throw new UnexpectedCharacterException("Parsing Object Key", "Finding object end or separator", this);
}

The method findObjectEndOrAttributeSep in the class io.nats.jparse.source.CharArrayCharSource is used to search for either the end of an object or an attribute separator in a character array.

Here is a step-by-step description of what this method is doing:

  1. Set the initial value of the variable i to the current index value.
  2. Set the initial value of the variable ch to zero.
  3. Get the reference to the character array data and the length of the array.
  4. Start a loop to iterate through the elements of the character array, starting from the current index i and ending at the last element of the array.
  5. Within the loop, assign the current element of the character array to the variable ch.
  6. Use a switch statement to check the value of ch against two possible cases: a. If ch matches the constant OBJECT_END_TOKEN, update the current index index to i + 1 and return true to indicate that the end of an object has been found. b. If ch matches the constant ATTRIBUTE_SEP, update the current index index to i and return false to indicate that an attribute separator has been found.
  7. If none of the above cases match for any element of the character array, throw an UnexpectedCharacterException with a detailed message indicating that the method was trying to parse an object key, while searching for either the object end or separator.
  8. End of the loop.

In summary, the method iterates through the character array data starting from the current index index and checks if each character matches either the object end token or the attribute separator. If a match is found, it updates the current index and returns a boolean value indicating the result. If no match is found, it throws an exception. sequence diagram

@Override

public boolean findCommaOrEndForArray()

@Override
public boolean findCommaOrEndForArray() {
    int i = index;
    char ch = 0;
    final char[] data = this.data;
    final int length = data.length;
    for (; i < length; i++) {
        ch = data[i];
        switch(ch) {
            case ARRAY_END_TOKEN:
                this.index = i + 1;
                return true;
            case ARRAY_SEP:
                this.index = i;
                return false;
            case NEW_LINE_WS:
            case CARRIAGE_RETURN_WS:
            case TAB_WS:
            case SPACE_WS:
                continue;
            default:
                throw new UnexpectedCharacterException("Parsing Object Key", "Finding object end or separator", this, ch, i);
        }
    }
    throw new UnexpectedCharacterException("Parsing Array", "Finding list end or separator", this);
}

The findCommaOrEndForArray method in the CharArrayCharSource class is used to find the comma or end token within an array. Here is a step-by-step description of what the method does:

  1. Start at the current index stored in the index variable.
  2. Initialize a temporary variable ch to store the current character being examined.
  3. Get the character array data from the instance variable this.data.
  4. Get the length of the character array in the length variable.
  5. Start a loop that iterates from the current index (i) to the length of the character array.
  6. Get the character at the current index (i) and store it in the ch variable.
  7. Use a switch statement to check the value of ch.
  8. If the character is equal to the ARRAY_END_TOKEN, set the index variable to i + 1, indicating that the end of the array has been found. Then, return true to indicate that the end token has been found.
  9. If the character is equal to the ARRAY_SEP, set the index variable to i, indicating that a comma separator has been found. Then, return false to indicate that a separator has been found.
  10. If the character is one of the defined whitespace characters (NEW_LINE_WS, CARRIAGE_RETURN_WS, TAB_WS, SPACE_WS), skip the current iteration of the loop and continue to the next character.
  11. If the character does not match any of the expected values, throw an UnexpectedCharacterException with the appropriate error message and include information about the current character and its index.
  12. If the end of the array is reached without finding an end token or separator, throw an UnexpectedCharacterException with the appropriate error message and context information about the parsing operation.

This method is used to parse an input character array and determine whether the next character is an end token or a separator within an array, based on the defined array tokens and whitespace characters. It returns a boolean value indicating whether an end token has been found or not. sequence diagram

@Override

public int findNullEnd()

@Override
public int findNullEnd() {
    if (this.data[++index] == 'u' && this.data[++index] == 'l' && this.data[++index] == 'l') {
        return ++index;
    } else {
        throw new UnexpectedCharacterException("Parsing JSON Null", "Unexpected character", this);
    }
}

The findNullEnd method in the CharArrayCharSource class is used to find the end position of a "null" value in a JSON string.

Here is a step-by-step description of what the method does:

  1. The method overrides the findNullEnd method defined in the superclass, indicating that it provides a specific implementation for this method.

  2. The method begins by incrementing the value of the index variable by 1. This is done using the pre-increment operator ++ before the index variable. This means that the new value of index will be one greater than its previous value.

  3. The method then checks if the character at the new index position in the data array is equal to the character 'u'. If it is, the condition is true and the code block inside the if statement is executed.

  4. Inside the if statement, the value of index is again incremented by 1 using the pre-increment operator. This means that the new value of index will be one greater than its previous value.

  5. The method then checks if the character at the new index position in the data array is equal to the character 'l'. If it is, the condition is true and the code block inside the if statement is executed.

  6. Inside the if statement, the value of index is again incremented by 1 using the pre-increment operator. This means that the new value of index will be one greater than its previous value.

  7. The method then checks if the character at the new index position in the data array is equal to the character 'l'. If it is, the condition is true and the code block inside the if statement is executed.

  8. Inside the if statement, the value of index is once again incremented by 1 using the pre-increment operator. This means that the new value of index will be one greater than its previous value.

  9. After the if-else block, the method returns the value of index, incremented by 1 using the pre-increment operator. This means that the returned value will be one greater than its previous value.

  10. If any of the if conditions in steps 3, 5, or 7 evaluate to false, indicating that the "null" value was not found, an UnexpectedCharacterException is thrown. This exception provides an error message stating that "Parsing JSON Null" failed because an unexpected character was encountered in the CharArrayCharSource, and includes a reference to the source object.

In summary, the findNullEnd method in the CharArrayCharSource class is responsible for locating the end position of a "null" value in a JSON string. It checks if the characters at a specific position in the string match the sequence 'u', 'l', 'l', and returns the position of the last character of the match. If the expected sequence is not found, an exception is thrown. sequence diagram

@Override

public boolean matchChars(final int startIndex, final int endIndex, CharSequence key)

@Override
public boolean matchChars(final int startIndex, final int endIndex, CharSequence key) {
    final int length = endIndex - startIndex;
    int idx = startIndex;
    switch(length) {
        case 1:
            return key.charAt(0) == data[idx];
        case 2:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1];
        case 3:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2];
        case 4:
            return key.charAt(0) == data[idx] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2] && key.charAt(3) == data[idx + 3];
        case 5:
            return key.charAt(1) == data[idx + 1] && key.charAt(3) == data[idx + 3] && key.charAt(0) == data[idx] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 6:
            return key.charAt(0) == data[idx] && key.charAt(5) == data[idx + 5] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 7:
            return key.charAt(0) == data[idx] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(5) == data[idx + 5] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4];
        case 8:
            return key.charAt(0) == data[idx] && key.charAt(7) == data[idx + 7] && key.charAt(3) == data[idx + 3] && key.charAt(1) == data[idx + 1] && key.charAt(5) == data[idx + 5] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(4) == data[idx + 4];
        case 9:
            return key.charAt(0) == data[idx] && key.charAt(8) == data[idx + 8] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1];
        case 10:
            return key.charAt(0) == data[idx] && key.charAt(9) == data[idx + 9] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        case 11:
            return key.charAt(0) == data[idx] && key.charAt(10) == data[idx + 10] && key.charAt(6) == data[idx + 6] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(9) == data[idx + 9] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        case 12:
            return key.charAt(0) == data[idx] && key.charAt(11) == data[idx + 11] && key.charAt(3) == data[idx + 3] && key.charAt(7) == data[idx + 7] && key.charAt(2) == data[idx + 2] && key.charAt(6) == data[idx + 6] && key.charAt(9) == data[idx + 9] && key.charAt(4) == data[idx + 4] && key.charAt(5) == data[idx + 5] && key.charAt(10) == data[idx + 10] && key.charAt(1) == data[idx + 1] && key.charAt(8) == data[idx + 8];
        default:
            final int start = 0;
            final int end = length - 1;
            final int middle = length / 2;
            if (key.charAt(start) == data[idx] && key.charAt(end) == data[idx + end] && key.charAt(middle) == data[idx + middle]) {
                for (int i = 1; i < length; i++) {
                    if (key.charAt(i) != data[idx + i]) {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
    }
}

The matchChars method is a method defined in the CharArrayCharSource class, which is used for comparing a character sequence with a portion of a character array.

Here's a step-by-step description of what the method does:

  1. The method takes three parameters: startIndex, endIndex, and key. These parameters indicate the range of the character array to be compared and the character sequence to compare it with.

  2. The method calculates the length of the character array range by subtracting the startIndex from the endIndex.

  3. It initializes a variable idx with the value of the startIndex.

  4. The method then uses a switch statement based on the length of the character array range:

    • For lengths 1 to 12, it compares each corresponding character in the key sequence with the corresponding character in the character array range using the charAt method. If all the characters match, it returns true, otherwise false.

    • For lengths greater than 12, it performs a more generic comparison. It checks if the first, middle, and last characters of the key sequence match the corresponding characters in the character array range. If they do, it iterates through the rest of the characters in the key sequence and character array range, comparing each character. If all the characters match, it returns true, otherwise false.

    • If none of the above cases match, it returns false.

Overall, the matchChars method is used to determine if a given character sequence matches a specific portion of a character array. It uses a combination of specific comparisons for shorter lengths and a more generic comparison for longer lengths. sequence diagram

public boolean isInteger(int offset, int end)

public boolean isInteger(int offset, int end) {
    int len = end - offset;
    final char[] digitChars = data;
    final boolean negative = (digitChars[offset] == '-');
    final int cmpLen = negative ? MIN_INT_STR_LENGTH : MAX_INT_STR_LENGTH;
    if (len < cmpLen)
        return true;
    if (len > cmpLen)
        return false;
    final char[] cmpStr = negative ? MIN_INT_CHARS : MAX_INT_CHARS;
    for (int i = 0; i < cmpLen; ++i) {
        int diff = digitChars[offset + i] - cmpStr[i];
        if (diff != 0) {
            return (diff < 0);
        }
    }
    return true;
}

The isInteger method, defined in the io.nats.jparse.source.CharArrayCharSource class, determines whether a sequence of characters in an array represents an integer. The method takes in two parameters - offset and end, which specify the range of characters in the array to be considered.

Here is a step-by-step description of what the method does based on its body:

  1. Calculate the length of the sequence by subtracting the offset from the end parameter and store it in the len variable.
  2. Get a reference to the character array (data) that contains the sequence of characters to be examined.
  3. Determine whether the first character in the sequence is a negative sign ('-') and store the result in the negative variable.
  4. Determine whether the length of the sequence (len) is less than a predefined length for comparisons (MIN_INT_STR_LENGTH or MAX_INT_STR_LENGTH). If it is, return true since the sequence is shorter than the minimum length required for a valid integer representation.
  5. If the length of the sequence is longer than the predefined length, return false since the sequence is longer than the maximum length required for a valid integer representation.
  6. Determine which predefined character array to use for comparison (MIN_INT_CHARS or MAX_INT_CHARS), based on whether the sequence represents a negative or positive integer, and store it in the cmpStr variable.
  7. Iterate over each character in the sequence, comparing it with the corresponding character in the predefined character array.
  8. In each iteration, calculate the difference between the current character in the sequence and the corresponding character in the predefined character array and store it in the diff variable.
  9. If the difference is non-zero, return true if the difference is less than zero, indicating that the sequence is a smaller number than the predefined comparison value.
  10. If all characters in the sequence match the corresponding characters in the predefined character array, return true to indicate that the sequence represents a valid integer.
  11. If no non-zero differences are found, return true since the sequence matches the predefined comparison value.

In summary, the isInteger method determines whether a sequence of characters in an array represents an integer by comparing it with predefined character arrays and performing character-by-character comparisons. It handles cases where the sequence is too short or too long, and also distinguishes between negative and positive integers. sequence diagram

@Override

public String errorDetails(String message, int index, int ch)

@Override
public String errorDetails(String message, int index, int ch) {
    StringBuilder buf = new StringBuilder(255);
    final char[] array = data;
    buf.append(message).append("\n");
    buf.append("\n");
    buf.append("The current character read is " + debugCharDescription(ch)).append('\n');
    int line = 0;
    int lastLineIndex = 0;
    for (int i = 0; i < index && i < array.length; i++) {
        if (array[i] == '\n') {
            line++;
            lastLineIndex = i + 1;
        }
    }
    int count = 0;
    for (int i = lastLineIndex; i < array.length; i++, count++) {
        if (array[i] == '\n') {
            break;
        }
    }
    buf.append("line number " + (line + 1)).append('\n');
    buf.append("index number " + index).append('\n');
    try {
        buf.append(new String(array, lastLineIndex, count)).append('\n');
    } catch (Exception ex) {
        try {
            int start = index = (index - 10 < 0) ? 0 : index - 10;
            buf.append(new String(array, start, index)).append('\n');
        } catch (Exception ex2) {
            buf.append(new String(array)).append('\n');
        }
    }
    for (int i = 0; i < (index - lastLineIndex); i++) {
        buf.append('.');
    }
    buf.append('^');
    return buf.toString();
}

Method errorDetails

This method, defined in the class io.nats.jparse.source.CharArrayCharSource, is used to generate an error message with details about the error encountered at a specific index in the character array.

Signature

public String errorDetails(String message, int index, int ch)

Parameters

  • message (String): A custom error message to prepend to the error details.
  • index (int): The index at which the error occurred in the character array.
  • ch (int): The current character being processed when the error occurred.

Return Value

  • String: The generated error message with details.

Steps

  1. Create a StringBuilder instance named buf, initialized with an initial capacity of 255.
  2. Create a local variable named array of type char[], initialized with the character array data from the class.
  3. Append the message parameter and a newline character to buf.
  4. Append an additional newline character to buf.
  5. Append a formatted string to buf that describes the current character being processed, using the debugCharDescription method.
  6. Initialize two local variables, line and lastLineIndex, both set to 0.
  7. Iterate from i = 0 to index, exclusive, and as long as i is within the length of array.
    • If the character array[i] is a newline character, increment line by 1 and update lastLineIndex to i + 1.
  8. Initialize two local variables, count set to 0, and i set to lastLineIndex.
  9. Iterate from i = lastLineIndex to the end of array.
    • If the character array[i] is a newline character, break the loop.
    • Otherwise, increment count by 1.
  10. Append a formatted string to buf that states the line number, which is line + 1.
  11. Append a formatted string to buf that states the index number, which is index.
  12. Attempt the following block, and catch any exceptions thrown:
    • Append a substring of array to buf, starting at lastLineIndex with a length of count.
  13. If an exception was caught in the above block, attempt the following block, and catch any exceptions thrown:
    • Calculate the start index by subtracting 10 from index. If the result is less than 0, set start to 0.
    • Append a substring of array to buf, starting at start with a length of index.
  14. If an exception was caught in the above block, append the entire array to buf.
  15. Append a sequence of dots (.) to buf, equal to the difference between index and lastLineIndex.
  16. Append a ^ character to buf.
  17. Return the string representation of buf. sequence diagram

io.nats.jparse.source.support

class diagram

ParseFloat

The ParseFloat class is a public class that is used for parsing floating-point values.

public static float parseFloat(char[] chars, int startIndex, int endIndex)

public static float parseFloat(char[] chars, int startIndex, int endIndex) {
    boolean negative = false;
    int i = startIndex;
    float result = 0;
    // Check for a negative sign
    if (chars[i] == '-') {
        negative = true;
        i++;
    }
    loop: while (i < endIndex) {
        char ch = chars[i];
        switch(ch) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                result = result * 10 + (ch - '0');
                i++;
                break;
            case '.':
                result = parseFractionPart(i + 1, endIndex, chars, result);
                break loop;
            case 'e':
                result = parseExponent(i + 1, endIndex, chars, result);
                break loop;
            default:
                throw new UnexpectedCharacterException("parsing float", "Illegal character", ch, i);
        }
    }
    if (negative) {
        result = -result;
    }
    return result;
}

The parseFloat method in the io.nats.jparse.source.support.ParseFloat class is a static method that takes three parameters: char[] chars, int startIndex, and int endIndex. It returns a float value.

Here is a step-by-step description of what the parseFloat method does based on its body:

  1. It initializes a boolean variable negative to false, an integer variable i to the value of startIndex, and a float variable result to 0.

  2. It checks if the character at index i in the chars array is '-'. If it is, it sets negativetotrueand incrementsi` by 1.

  3. It enters a loop that continues until i is less than endIndex.

  4. Inside the loop, it assigns the character at index i in the chars array to a char variable ch.

  5. It checks the value of ch using a switch statement. If ch is a digit from 0 to 9, it performs the following steps:

    • Multiplies the result by 10 and adds the numeric value of ch minus the character '0' (to convert from ASCII to the actual numeric value).
    • Increments i by 1.
    • Breaks out of the switch statement.
  6. If ch is a '.', it calls a method parseFractionPart with parameters i + 1, endIndex, chars, and result and assigns the return value to result.

  7. If ch is an 'e', it calls a method parseExponent with parameters i + 1, endIndex, chars, and result and assigns the return value to result.

  8. If ch does not match any of the cases in the switch statement, it throws an UnexpectedCharacterException with the message "Illegal character" and the values of "parsing float", the actual character ch, and the index i.

  9. After the loop ends, it checks if negative is true. If it is, it negates the value of result.

  10. Finally, it returns the value of result.

Note: The parseFractionPart and parseExponent methods are not included in the given code, so their behavior is not described here. sequence diagram

private static float parseFractionPart(int i, int endIndex, char[] chars, float result)

private static float parseFractionPart(int i, int endIndex, char[] chars, float result) {
    float fraction = 0.1f;
    while (i < endIndex) {
        char ch = chars[i];
        switch(ch) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                result += (ch - '0') * fraction;
                fraction /= 10;
                i++;
                break;
            case 'e':
                return parseExponent(i + 1, endIndex, chars, result);
            default:
                throw new UnexpectedCharacterException("float parsing fraction part", "Illegal character", ch, i);
        }
    }
    return result;
}

The parseFractionPart method, defined in the ParseFloat class of the io.nats.jparse.source.support package, is used to parse and calculate the fraction part of a floating-point number.

Here is a step-by-step description of what the method is doing:

  1. The method takes the following parameters: i (the starting index of the fraction part), endIndex (the index at which the fraction part ends), chars (an array of characters representing the input number), and result (the current result of the parsing operation).

  2. It initializes a variable fraction as 0.1.

  3. It enters a while loop that iterates from i to endIndex.

  4. Within the loop, the method retrieves the character at the current index (i) from the chars array and assigns it to a variable ch.

  5. It uses a switch statement to perform different actions based on the character ch.

  6. If the character ch is a digit ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), the method calculates the corresponding fractional value and updates the result accordingly. It multiplies the difference between ch and '0' by fraction and adds it to the result. It then divides fraction by 10 to handle the next digit and increments i to move to the next character.

  7. If the character ch is 'e', the method returns the result of another method called parseExponent (which is not defined in the given code snippet).

  8. If the character ch is neither a digit nor 'e', the method throws an UnexpectedCharacterException with an appropriate error message.

  9. After each iteration of the loop, i is incremented to process the next character.

  10. Once the loop completes, the method returns the final result.

Note: The given code snippet only includes the implementation of the parseFractionPart method. It is assumed that the parseExponent method is defined elsewhere in the code. sequence diagram

private static float parseExponent(int i, int endIndex, char[] chars, float result)

private static float parseExponent(int i, int endIndex, char[] chars, float result) {
    boolean exponentNegative = false;
    int exponent = 0;
    char sign = chars[i];
    switch(sign) {
        case '-':
            exponentNegative = true;
            i++;
            break;
        case '+':
            i++;
            break;
    }
    while (i < endIndex) {
        char ch = chars[i];
        switch(chars[i]) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                exponent = exponent * 10 + (ch - '0');
                i++;
                break;
            default:
                throw new UnexpectedCharacterException("float parsing exponent part", "Illegal character", ch, i);
        }
    }
    if (exponentNegative) {
        exponent = -exponent;
    }
    // Use Lookup table for powers of 10
    // Calculate the power of 10
    if (!exponentNegative) {
        while (exponent >= powersOf10.length) {
            result *= 1e18f;
            exponent -= 18;
        }
        result *= powersOf10[exponent];
    } else {
        while (-exponent >= powersOf10.length) {
            result /= 1e18f;
            exponent += 18;
        }
        result /= powersOf10[-exponent];
    }
    return result;
}

The method parseExponent is used to parse the exponent part of a floating-point number. Here is a step-by-step description of what the method is doing based on its body:

  1. It accepts four parameters: i (the starting index of the exponent part), endIndex (the ending index of the exponent part), chars (the array of characters representing the number), and result (the current result of parsing the float value).

  2. It initializes a boolean variable exponentNegative to false, which will indicate if the exponent is negative.

  3. It initializes an integer variable exponent to store the parsed exponent value.

  4. It retrieves the character at the index i from the chars array and assigns it to the variable sign.

  5. It checks the value of sign using a switch statement:

    • If sign is '-' (indicating a negative exponent), it sets exponentNegative to true and increments i by 1.
    • If sign is '+' (indicating a positive exponent), it increments i by 1.
  6. It enters a while loop that iterates while the index i is less than endIndex.

  7. Inside the loop, it retrieves the character at the index i from the chars array and assigns it to the variable ch.

  8. It checks the value of ch using a switch statement:

    • If ch is a digit from '0' to '9', it converts the character to its corresponding integer value and adds it to the exponent variable. It then increments i by 1.
    • If ch is not a digit, it throws an UnexpectedCharacterException with an appropriate error message indicating that an illegal character was encountered during parsing.
  9. After the loop, it checks if exponentNegative is true. If so, it negates the value of exponent.

  10. It enters an if-else condition to calculate the power of 10 based on the value of exponent and update the result accordingly:

    • If exponentNegative is false (indicating a positive exponent), it enters a while loop that continues while exponent is greater than or equal to powersOf10.length. Inside the loop, it multiplies result by 1e18f (a constant representing 10^18) and subtracts 18 from exponent. Finally, it multiplies result by the power of 10 stored in the powersOf10 array at the index exponent.
    • If exponentNegative is true (indicating a negative exponent), it enters a while loop that continues while the negative value of exponent is greater than or equal to powersOf10.length. Inside the loop, it divides result by 1e18f and adds 18 to the negative exponent. Finally, it divides result by the power of 10 stored in the powersOf10 array at the index -exponent.
  11. Finally, it returns the updated result.

Note: The method assumes the existence of a powersOf10 array, which is not defined in the given code snippet. sequence diagram

UnexpectedCharacterException

This class represents an exception that is thrown when an unexpected character is encountered during parsing. It is used in cases where an unexpected character is encountered while parsing a JSON string or CharSource. This exception can be thrown by the JsonParser class in the io.nats.jparse.parser package. For more information, refer to the CharSource class and the JsonParser class.

PathException

The PathException class is a runtime exception that is used to handle errors related to JSON paths. It is thrown when an unexpected condition is encountered while processing a path.

ParseDouble

The ParseDouble class is a class that is used to parse and convert string representations of numbers into the double data type.

public static double parseDouble(char[] chars, int startIndex, int endIndex)

public static double parseDouble(char[] chars, int startIndex, int endIndex) {
    boolean negative = false;
    int i = startIndex;
    double result = 0;
    // Check for a negative sign
    if (chars[i] == '-') {
        negative = true;
        i++;
    }
    loop: while (i < endIndex) {
        char ch = chars[i];
        switch(ch) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                result = result * 10 + (ch - '0');
                i++;
                break;
            case '.':
                result = parseFractionPart(i + 1, endIndex, chars, result);
                break loop;
            case 'E':
            case 'e':
                result = parseExponent(i + 1, endIndex, chars, result);
                break loop;
            default:
                throw new UnexpectedCharacterException("parsing double", "Illegal character", ch, i);
        }
    }
    if (negative) {
        result = -result;
    }
    return result;
}

The parseDouble method defined in the ParseDouble class in the io.nats.jparse.source.support package is used to parse a double value from a character array.

Here is a step-by-step description of what the method does based on its body:

  1. Initialize a boolean variable negative to false.
  2. Initialize an integer variable i to the startIndex parameter.
  3. Initialize a double variable result to 0.
  4. Check if the character at index i in the chars array is a negative sign ('-'). If it is, set negative to true and increment i by 1.
  5. Enter a loop that iterates while i is less than endIndex.
  6. Get the character at index i in the chars array and assign it to the variable ch.
  7. Use a switch statement to check the value of ch:
    • If ch is a digit ('0' to '9'), multiply result by 10 and add the numerical value of ch (obtained by subtracting the character '0') to it. Then, increment i by 1.
    • If ch is a period ('.'), call the parseFractionPart method with the parameters i + 1, endIndex, chars, and result. Assign the returned value to result, and break the loop using the break loop statement.
    • If ch is an 'E' or 'e', call the parseExponent method with the parameters i + 1, endIndex, chars, and result. Assign the returned value to result, and break the loop using the break loop statement.
    • If ch is none of the above, throw an UnexpectedCharacterException with the appropriate message, character, and position.
  8. If negative is true, negate result.
  9. Return the value of result.

private static double parseFractionPart(int i, int endIndex, char[] chars, double result)

private static double parseFractionPart(int i, int endIndex, char[] chars, double result) {
    double fraction = 0.1;
    while (i < endIndex) {
        char ch = chars[i];
        switch(ch) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                result += (ch - '0') * fraction;
                fraction /= 10;
                i++;
                break;
            case 'E':
            case 'e':
                return parseExponent(i + 1, endIndex, chars, result);
            default:
                throw new UnexpectedCharacterException("double parsing fraction part", "Illegal character", ch, i);
        }
    }
    return result;
}

Method: parseFractionPart

Class: io.nats.jparse.source.support.ParseDouble

Description: This method is used to parse the fraction part of a double number represented as a character array. It iterates through each character in the fraction part, calculates the numeric value of the fraction, and updates the overall result.

Parameters:

  • i (int): The current index of the character being processed in the fraction part.
  • endIndex (int): The index of the last character in the fraction part.
  • chars (char[]): The character array containing the fraction part of the double number.
  • result (double): The accumulated result of parsing the fraction part.

Returns:

  • double: The final result after parsing the fraction part.

Steps:

  1. Initialize the fraction variable to 0.1.
  2. Start a while loop with the condition i < endIndex.
  3. Fetch the character at the current index from the character array and store it in the variable ch.
  4. Use a switch statement to perform actions based on the value of ch:
    • If ch is a digit (0-9), perform the following steps:
      • Convert the character to its numeric value by subtracting '0' from it.
      • Multiply the numeric value by the fraction variable.
      • Add the result to the updated numeric value.
      • Divide the fraction variable by 10 to decrease its value.
      • Increment i by 1 to move to the next character.
      • Break out of the switch statement.
    • If ch is 'E' or 'e', return the result of calling the parseExponent method with the appropriate parameters.
    • If ch is neither a digit nor 'E' or 'e', throw an UnexpectedCharacterException with an appropriate error message.
  5. After the while loop ends, return the final result.

Please let me know if you need more information. sequence diagram

private static double parseExponent(int i, int endIndex, char[] chars, double result)

private static double parseExponent(int i, int endIndex, char[] chars, double result) {
    boolean exponentNegative = false;
    int exponent = 0;
    char sign = chars[i];
    switch(sign) {
        case '-':
            exponentNegative = true;
            i++;
            break;
        case '+':
            i++;
            break;
    }
    while (i < endIndex) {
        char ch = chars[i];
        switch(chars[i]) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                exponent = exponent * 10 + (ch - '0');
                i++;
                break;
            default:
                throw new UnexpectedCharacterException("double parsing parsing exponent", "Illegal character", ch, i);
        }
    }
    if (exponentNegative) {
        exponent = -exponent;
    }
    // Use Lookup table for powers of 10
    // Calculate the power of 10
    if (!exponentNegative) {
        while (exponent >= powersOf10.length) {
            result *= 1e22;
            exponent -= 22;
        }
        result *= powersOf10[exponent];
    } else {
        while (-exponent >= powersOf10.length) {
            result /= 1e22;
            exponent += 22;
        }
        result /= powersOf10[-exponent];
    }
    return result;
}

The parseExponent method in class io.nats.jparse.source.support.ParseDouble is used to parse the exponent part of a floating-point number represented as a char array.

Here is a step-by-step description of what the method does:

  1. The method takes in four parameters: i (the current index of the char array), endIndex (the index of the last character in the char array), chars (the char array containing the number), and result (the parsed value of the number so far).

  2. The method initializes a boolean variable exponentNegative to false and an integer variable exponent to 0. It also stores the value of the i-th character in the chars array in a variable sign.

  3. The method then enters a switch statement based on the value of sign: a. If sign is '-', the exponentNegative variable is set to true and the value of i is incremented. b. If sign is '+', the value of i is incremented.

  4. The method then enters a while loop that continues as long as i is less than endIndex.

  5. Inside the loop, the method retrieves the character at the i-th index of the chars array and enters a switch statement based on its value. a. If the character is a digit (0-9), the value of exponent is updated by multiplying it by 10 and adding the difference between the character value and the character value of '0'. The value of i is also incremented. b. If the character is not a digit, an UnexpectedCharacterException is thrown with an appropriate error message.

  6. After the while loop, the method checks if exponentNegative is true. If it is, the value of exponent is negated.

  7. The method then enters an if statement to handle the calculation of the power of 10. a. If exponentNegative is false, a while loop is initiated that runs as long as exponent is greater than or equal to the length of powersOf10 (presumably an array storing pre-calculated powers of 10).

    • Inside the loop, result is multiplied by 1e22 (which is 10 raised to the power 22) and exponent is decreased by 22. b. Finally, result is multiplied by the power of 10 corresponding to exponent.
  8. If exponentNegative is true, a similar while loop is initiated but with negated exponent and division instead of multiplication.

  9. Finally, the method returns the result.

Note: The code snippet provided doesn't include the definition of the powersOf10 array, so it's assumed that it's defined somewhere else in the code. sequence diagram

CharArraySegment

The CharArraySegment class is a public class that implements the CharSequence interface. It represents a segment of a character array (char[]), and provides access to a subsequence of the array as a CharSequence.

io.nats.jparse.path

class diagram

PathNode

The PathNode class is a representation of a node in the parsed path of a structured data source, such as JSON or XML. It is a subclass of AbstractList and implements the CollectionNode interface.

@Override

public List<List> childrenTokens()

@Override
public List<List<Token>> childrenTokens() {
    if (childrenTokens == null) {
        childrenTokens = Arrays.stream(tokens.toArray()).map(Collections::singletonList).collect(Collectors.toList());
    }
    return childrenTokens;
}

The childrenTokens method in the PathNode class is responsible for returning a list of token lists. Here is a step-by-step description of what this method is doing:

  1. The method overrides the childrenTokens method from the superclass, indicating that it provides a custom implementation for this method.

  2. The method checks if the childrenTokens variable is null. This variable is an instance variable of the PathNode class.

  3. If the childrenTokens variable is indeed null, the method proceeds to the next step. Otherwise, it directly returns the childrenTokens variable.

  4. Within the if block, the method creates a stream from the tokens list using the Arrays.stream method. The tokens list is an instance variable of the PathNode class.

  5. The stream of tokens is then mapped to a list of single-element lists using the map method. Each token is wrapped in a singleton list using the Collections::singletonList method reference.

  6. The resulting mapped stream is collected into a list of token lists using the collect method. The Collectors.toList() method is used to collect the elements into a List.

  7. The childrenTokens variable is assigned the newly created list of token lists.

  8. Finally, the method returns the childrenTokens variable.

In summary, the childrenTokens method returns a list of token lists. If the childrenTokens variable is null, it creates a new list by wrapping each token in the tokens list in a singleton list. This list is then assigned to the childrenTokens variable and returned. If the childrenTokens variable is not null, it is directly returned.

The childrenTokens method in the io.nats.jparse.path.PathNode class is overridden to return a list of token lists.

Within the method, it first checks if the childrenTokens variable is null. If it is null, it performs the following logic: it converts the array of tokens to a stream, maps each token to a singleton list using the Collections::singletonList method, and finally collects the singleton lists into a list using the Collectors.toList method.

Once the childrenTokens list is created, it is stored in the childrenTokens variable for future use. Finally, the method returns the childrenTokens list.

In summary, the childrenTokens method returns a list of token lists, where each token list represents a child of the PathNode.

sequence diagram

Node[] elements()

Node[] elements() {
    if (elements == null) {
        elements = new Node[tokens.size()];
    }
    return elements;
}

The elements method defined in the class io.nats.jparse.path.PathNode returns an array of Node objects.

Here is a step-by-step description of what this method is doing based on its body:

  1. Check if the variable elements is null.

  2. If elements is null, it means that the array has not been initialized yet.

  3. In this case, we create a new array of Node objects with a size equal to the number of elements in the tokens list.

  4. Assign the newly created array to the elements variable.

  5. Finally, return the elements array.

Essentially, this method ensures that the elements array is initialized and properly sized before returning it. If the array is already initialized, the method simply returns the existing array without any modification.

The method elements() in the io.nats.jparse.path.PathNode class is used to retrieve an array of Node objects.

First, it checks if the elements array is null. If it is null, it creates a new array with a size equal to the number of tokens. This is done to ensure that the elements array is initialized and has the same size as the list of tokens.

Finally, it returns the elements array, which may be either the newly created array or the already existing array.

sequence diagram

@Override

public Iterator iterator()

@Override
public Iterator<PathElement> iterator() {
    return new Iterator<PathElement>() {

        int index = 0;

        @Override
        public boolean hasNext() {
            return index < tokens().size();
        }

        @Override
        public PathElement next() {
            return (PathElement) getNodeAt(index++);
        }
    };
}

The iterator() method in the class io.nats.jparse.path.PathNode returns an iterator for iterating over the PathElement objects in the tokens list.

Here is a step by step description of what the method is doing:

  1. The method defines an anonymous inner class that implements the Iterator interface.
  2. The inner class has a variable index initialized to 0, which represents the current index in the tokens list.
  3. The hasNext() method is overridden to check if the index is less than the size of the tokens list, indicating whether there are more elements to iterate over.
  4. The next() method is overridden to return the PathElement object at the current index in the tokens list using the getNodeAt() method, and then increments the index by 1.
  5. The inner class is instantiated and returned as the iterator for the PathNode object.

So, the iterator() method creates an iterator that allows iterating over the PathElement objects in the tokens list by implementing the hasNext() and next() methods.

The iterator method in the io.nats.jparse.path.PathNode class returns an iterator object that allows iterating over a collection of PathElement objects.

The implementation overrides the hasNext method to check if there are more elements in the collection to iterate through, and the next method returns the next PathElement object in the collection.

The iterator is implemented as an anonymous inner class that keeps track of the current index in the collection and returns the element at that index when next is called.

sequence diagram

PathParser

The PathParser class is a public class that implements the JsonParser interface. It provides similar functionality for parsing Json Paths as JSONPath expressions.

private List scan(CharSource source, TokenList tokens)

private List<Token> scan(CharSource source, TokenList tokens) {
    char ch = ' ';
    loop: while (true) {
        ch = (char) source.next();
        switch(ch) {
            case ParseConstants.INDEX_BRACKET_START_TOKEN:
                parseIndexOrKey(source, (char) source.next(), tokens);
                break;
            //'A';
            case ParseConstants.A:
            //'B';
            case ParseConstants.B:
            //'C';
            case ParseConstants.C:
            //'D';
            case ParseConstants.D:
            //'E';
            case ParseConstants.E:
            //'F';
            case ParseConstants.F:
            //'G';
            case ParseConstants.G:
            //'H';
            case ParseConstants.H:
            //'I';
            case ParseConstants.I:
            //'J';
            case ParseConstants.J:
            //'K';
            case ParseConstants.K:
            //'L';
            case ParseConstants.L:
            //'M';
            case ParseConstants.M:
            //'N';
            case ParseConstants.N:
            //'O';
            case ParseConstants.O:
            //'P';
            case ParseConstants.P:
            //'Q';
            case ParseConstants.Q:
            //'R';
            case ParseConstants.R:
            //'S';
            case ParseConstants.S:
            //'T';
            case ParseConstants.T:
            //'U';
            case ParseConstants.U:
            //'V';
            case ParseConstants.V:
            //'W';
            case ParseConstants.W:
            //'X';
            case ParseConstants.X:
            //'Y';
            case ParseConstants.Y:
            //'Z';
            case ParseConstants.Z:
            // = 'a';
            case ParseConstants.A_:
            //'b';
            case ParseConstants.B_:
            //'c';
            case ParseConstants.C_:
            //'d';
            case ParseConstants.D_:
            //'e';
            case ParseConstants.E_:
            //'f';
            case ParseConstants.F_:
            //'g';
            case ParseConstants.G_:
            //'h';
            case ParseConstants.H_:
            //'i';
            case ParseConstants.I_:
            //'j';
            case ParseConstants.J_:
            //'k';
            case ParseConstants.K_:
            //'l';
            case ParseConstants.L_:
            //'m';
            case ParseConstants.M_:
            //'n';
            case ParseConstants.N_:
            //'o';
            case ParseConstants.O_:
            //'p';
            case ParseConstants.P_:
            //'q';
            case ParseConstants.Q_:
            //'r';
            case ParseConstants.R_:
            //'s';
            case ParseConstants.S_:
            //'t';
            case ParseConstants.T_:
            //'u';
            case ParseConstants.U_:
            //'v';
            case ParseConstants.V_:
            //'w';
            case ParseConstants.W_:
            //'x';
            case ParseConstants.X_:
            //'y';
            case ParseConstants.Y_:
            case //'z';
            ParseConstants.Z_:
                parseKeyName(source, ch, tokens);
                break;
            case ParseConstants.DOT:
                parseKeyName(source, (char) source.next(), tokens);
                break;
            case ParseConstants.ETX:
                break loop;
            default:
                throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
        }
    }
    return tokens;
}

Method: scan

This method is defined in the class io.nats.jparse.path.PathParser.

Purpose

The purpose of the scan method is to scan a CharSource and tokenize it, storing the tokens in a TokenList. The method performs a series of checks on each character in the input source and determines the appropriate action based on the character.

Parameters

  • source (type: CharSource): The source containing the characters to be scanned.
  • tokens (type: TokenList): The list to store the generated tokens.

Steps

  1. Initialize the variable ch with a space character ' '.
  2. Enter an infinite loop labeled "loop".
  3. Inside the loop:
    • Assign the next character from the source to ch.
    • Start a switch statement based on the value of ch.
    1. Case: ParseConstants.INDEX_BRACKET_START_TOKEN
      • Call the method parseIndexOrKey with the source, the next character from source, and tokens as arguments.
      • Break out of the switch statement.
    2. Cases: ParseConstants.A, ParseConstants.B, ..., ParseConstants.Z, ParseConstants.A_, ParseConstants.B_, ..., ParseConstants.Z_
      • Call the method parseKeyName with the source, ch, and tokens as arguments.
      • Break out of the switch statement.
    3. Case: ParseConstants.DOT
      • Call the method parseKeyName with the source and the next character from source as arguments.
      • Break out of the switch statement.
    4. Case: ParseConstants.ETX
      • Break out of the loop labeled "loop".
    5. Default case:
      • Throw an IllegalStateException with a message indicating that the character ch cannot be understood with its index in the source.
  4. Return the tokens list.

Note: The method scans each character in the source one by one until it encounters an ETX character, which signifies the end of the input. During the scanning process, it performs specific actions based on the type of character encountered, such as parsing index, key, or throwing an exception for an unknown character.

The method scan in the class PathParser is used to scan the given source character by character and tokenize it based on certain conditions.

The method starts by initializing a loop that continues until it encounters a break statement. Inside the loop, it reads the next character from the source and assigns it to the variable ch.

Then, based on the value of ch, it performs different actions. If ch is equal to [ character, it calls the parseIndexOrKey method passing the next character from the source and the tokens list.

If ch is equal to any uppercase or lowercase letter (A to Z, a to z), it calls the parseKeyName method passing the source, ch, and tokens as arguments.

If ch is equal to . character, it calls the parseKeyName method passing the next character from the source and the tokens list.

If ch is equal to ETX character, it breaks the loop and the method returns the tokens list.

If ch does not match any of the above cases, it throws an IllegalStateException with an error message indicating the character ch and its index in the source.

private void parseIndexOrKey(CharSource source, char ch, TokenList tokens)

private void parseIndexOrKey(CharSource source, char ch, TokenList tokens) {
    final int startIndex = source.getIndex();
    switch(ch) {
        case ParseConstants.NUM_0:
        case ParseConstants.NUM_1:
        case ParseConstants.NUM_2:
        case ParseConstants.NUM_3:
        case ParseConstants.NUM_4:
        case ParseConstants.NUM_5:
        case ParseConstants.NUM_6:
        case ParseConstants.NUM_7:
        case ParseConstants.NUM_8:
        case ParseConstants.NUM_9:
            parseIndex(source, startIndex, tokens, ch);
            break;
        case ParseConstants.SINGLE_QUOTE:
            parseKeyWithQuotes(source, startIndex + 1, tokens, ch);
            break;
        case ParseConstants.ETX:
            throw new IllegalStateException("reached end");
        default:
            throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
    }
}

Method: parseIndexOrKey

Description:

The parseIndexOrKey method is a private method defined in the PathParser class in the io.nats.jparse.path package. This method is responsible for parsing an index or a key from a given character source.

Parameters:

  • source: The character source from which to parse the index or key.
  • ch: The current character being processed.
  • tokens: The list of tokens where the parsed index or key will be added.

Steps:

  1. Get the start index of the character source.
  2. Switch based on the value of ch:
    • If ch is one of the constant values ParseConstants.NUM_0, ParseConstants.NUM_1, ParseConstants.NUM_2, ParseConstants.NUM_3, ParseConstants.NUM_4, ParseConstants.NUM_5, ParseConstants.NUM_6, ParseConstants.NUM_7, ParseConstants.NUM_8, ParseConstants.NUM_9, then call the parseIndex method with the source, startIndex, tokens, and ch as parameters.
    • If ch is equal to the constant value ParseConstants.SINGLE_QUOTE, then call the parseKeyWithQuotes method with the source, startIndex + 1, tokens, and ch as parameters.
    • If ch is equal to the constant value ParseConstants.ETX, then throw an IllegalStateException with the message "reached end".
    • If none of the above conditions are met, then throw an IllegalStateException with the message "Unable to understand char <ch> index <source.getIndex()>".

Return Type:

This method does not have a return type.

The parseIndexOrKey method, defined in the class PathParser in the io.nats.jparse.path package, is responsible for parsing an index or key from a given CharSource and adding it to the provided TokenList.

The method takes three parameters: source, which represents the source of characters to parse; ch, which is the current character to be processed; and tokens, which is the list where the parsed index or key will be added.

Inside the method, it first determines the startIndex of the current CharSource. Then, based on the value of ch, it performs one of the following actions:

  • If ch is a digit from 0 to 9, the method calls the parseIndex method passing the source, startIndex, tokens, and ch as parameters. This indicates that an index is being parsed.
  • If ch is a single quote ('), the method calls the parseKeyWithQuotes method passing the source, startIndex + 1, tokens, and ch as parameters. This indicates that a key enclosed in quotes is being parsed.
  • If ch is the end of text (ETX), the method throws an IllegalStateException with the message "reached end". This indicates that the end of the input has been reached unexpectedly.
  • If none of the above conditions are met, the method throws an IllegalStateException with the message "Unable to understand char [ch] index [source.getIndex()]". This indicates that the provided character is not recognized and cannot be parsed.

In summary, the parseIndexOrKey method in the PathParser class parses either an index or a key from a given CharSource based on the provided character. Depending on the character value, it either calls parseIndex, parseKeyWithQuotes, or throws an exception if the character is not recognized.

sequence diagram

private void parseKeyWithQuotes(CharSource source, int startIndex, TokenList tokens, char ch)

private void parseKeyWithQuotes(CharSource source, int startIndex, TokenList tokens, char ch) {
    loop: while (true) {
        ch = (char) source.next();
        switch(ch) {
            //'A';
            case ParseConstants.A:
            //'B';
            case ParseConstants.B:
            //'C';
            case ParseConstants.C:
            //'D';
            case ParseConstants.D:
            //'E';
            case ParseConstants.E:
            //'F';
            case ParseConstants.F:
            //'G';
            case ParseConstants.G:
            //'H';
            case ParseConstants.H:
            //'I';
            case ParseConstants.I:
            //'J';
            case ParseConstants.J:
            //'K';
            case ParseConstants.K:
            //'L';
            case ParseConstants.L:
            //'M';
            case ParseConstants.M:
            //'N';
            case ParseConstants.N:
            //'O';
            case ParseConstants.O:
            //'P';
            case ParseConstants.P:
            //'Q';
            case ParseConstants.Q:
            //'R';
            case ParseConstants.R:
            //'S';
            case ParseConstants.S:
            //'T';
            case ParseConstants.T:
            //'U';
            case ParseConstants.U:
            //'V';
            case ParseConstants.V:
            //'W';
            case ParseConstants.W:
            //'X';
            case ParseConstants.X:
            //'Y';
            case ParseConstants.Y:
            //'Z';
            case ParseConstants.Z:
            // = 'a';
            case ParseConstants.A_:
            //'b';
            case ParseConstants.B_:
            //'c';
            case ParseConstants.C_:
            //'d';
            case ParseConstants.D_:
            //'e';
            case ParseConstants.E_:
            //'f';
            case ParseConstants.F_:
            //'g';
            case ParseConstants.G_:
            //'h';
            case ParseConstants.H_:
            //'i';
            case ParseConstants.I_:
            //'j';
            case ParseConstants.J_:
            //'k';
            case ParseConstants.K_:
            //'l';
            case ParseConstants.L_:
            //'m';
            case ParseConstants.M_:
            //'n';
            case ParseConstants.N_:
            //'o';
            case ParseConstants.O_:
            //'p';
            case ParseConstants.P_:
            //'q';
            case ParseConstants.Q_:
            //'r';
            case ParseConstants.R_:
            //'s';
            case ParseConstants.S_:
            //'t';
            case ParseConstants.T_:
            //'u';
            case ParseConstants.U_:
            //'v';
            case ParseConstants.V_:
            //'w';
            case ParseConstants.W_:
            //'x';
            case ParseConstants.X_:
            //'y';
            case ParseConstants.Y_:
            //'z';
            case ParseConstants.Z_:
            case ParseConstants.NEW_LINE_WS:
            case ParseConstants.CARRIAGE_RETURN_WS:
            case ParseConstants.TAB_WS:
            case ParseConstants.SPACE_WS:
                continue;
            case ParseConstants.SINGLE_QUOTE:
                break loop;
            case ParseConstants.ETX:
                throw new IllegalStateException("reached end");
            default:
                if (ch > 20 && ch < 127) {
                    break;
                } else {
                    throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
                }
        }
    }
    final int endIndex = source.getIndex();
    int i = source.nextSkipWhiteSpace();
    if (i == ParseConstants.INDEX_BRACKET_END_TOKEN) {
        tokens.add(new Token(startIndex, endIndex, TokenTypes.PATH_KEY_TOKEN));
    } else {
        throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
    }
}

The parseKeyWithQuotes method is defined in the io.nats.jparse.path.PathParser class. It is responsible for parsing a key with quotes from a given source of characters. Here is a step-by-step description of what the method does:

  1. The method takes four parameters: source, startIndex, tokens, and ch.
  2. The method starts an infinite loop using a while statement with the label loop.
  3. Inside the loop, it retrieves the next character from the source using source.next() and assigns it to ch.
  4. It then checks the value of ch using a switch statement.
  5. If ch matches any of the constants defined in the ParseConstants class corresponding to characters from 'A' to 'Z', 'a' to 'z', or whitespace characters, it continues to the next iteration of the loop.
  6. If ch matches the constant ParseConstants.SINGLE_QUOTE, it breaks out of the loop using the break statement with the label loop.
  7. If ch matches the constant ParseConstants.ETX, it throws an IllegalStateException with the message "reached end".
  8. If none of the above conditions match, it checks if ch is a printable ASCII character by comparing its value with the range of 20 to 127. If it is, it breaks out of the loop. Otherwise, it throws an IllegalStateException with the message "Unable to understand char " + ch + " index " + source.getIndex()".
  9. After breaking out of the loop, it assigns the current index of the source to the variable endIndex.
  10. It then calls the nextSkipWhiteSpace() method on the source to get the next non-whitespace character and assigns it to the variable i.
  11. If i equals the constant ParseConstants.INDEX_BRACKET_END_TOKEN, it adds a new Token object to the tokens list with the startIndex, endIndex, and TokenTypes.PATH_KEY_TOKEN.
  12. If i does not match the constant ParseConstants.INDEX_BRACKET_END_TOKEN, it throws an IllegalStateException with the message "Unable to understand char " + ch + " index " + source.getIndex()".

Note: The exact behavior and purpose of the method may depend on the implementation details of the CharSource and TokenList classes, which are not provided in the given code snippet.

The parseKeyWithQuotes method in the PathParser class is responsible for parsing a key with quotes from a given character source. It iterates through the characters in the source and performs various checks and validations. If the character is a valid key character (including upper and lower case letters and the underscore), or a whitespace character, it continues to the next character. If the character is a single quote, it breaks out of the loop. If the character is the ETX (end of transmission) character, it throws an exception. If the character is not a valid key character or whitespace, it throws an exception indicating that it is unable to understand the character.

Finally, it retrieves the end index from the character source and checks the next character after skipping whitespace. If the next character is the closing bracket token, it adds a new token to the token list representing the parsed key. Otherwise, it throws an exception indicating that it is unable to understand the character.

private void parseKeyName(CharSource source, char ch, TokenList tokens)

private void parseKeyName(CharSource source, char ch, TokenList tokens) {
    final int startIndex = source.getIndex();
    loop: while (true) {
        ch = (char) source.next();
        switch(ch) {
            //'A';
            case ParseConstants.A:
            //'B';
            case ParseConstants.B:
            //'C';
            case ParseConstants.C:
            //'D';
            case ParseConstants.D:
            //'E';
            case ParseConstants.E:
            //'F';
            case ParseConstants.F:
            //'G';
            case ParseConstants.G:
            //'H';
            case ParseConstants.H:
            //'I';
            case ParseConstants.I:
            //'J';
            case ParseConstants.J:
            //'K';
            case ParseConstants.K:
            //'L';
            case ParseConstants.L:
            //'M';
            case ParseConstants.M:
            //'N';
            case ParseConstants.N:
            //'O';
            case ParseConstants.O:
            //'P';
            case ParseConstants.P:
            //'Q';
            case ParseConstants.Q:
            //'R';
            case ParseConstants.R:
            //'S';
            case ParseConstants.S:
            //'T';
            case ParseConstants.T:
            //'U';
            case ParseConstants.U:
            //'V';
            case ParseConstants.V:
            //'W';
            case ParseConstants.W:
            //'X';
            case ParseConstants.X:
            //'Y';
            case ParseConstants.Y:
            //'Z';
            case ParseConstants.Z:
            // = 'a';
            case ParseConstants.A_:
            //'b';
            case ParseConstants.B_:
            //'c';
            case ParseConstants.C_:
            //'d';
            case ParseConstants.D_:
            //'e';
            case ParseConstants.E_:
            //'f';
            case ParseConstants.F_:
            //'g';
            case ParseConstants.G_:
            //'h';
            case ParseConstants.H_:
            //'i';
            case ParseConstants.I_:
            //'j';
            case ParseConstants.J_:
            //'k';
            case ParseConstants.K_:
            //'l';
            case ParseConstants.L_:
            //'m';
            case ParseConstants.M_:
            //'n';
            case ParseConstants.N_:
            //'o';
            case ParseConstants.O_:
            //'p';
            case ParseConstants.P_:
            //'q';
            case ParseConstants.Q_:
            //'r';
            case ParseConstants.R_:
            //'s';
            case ParseConstants.S_:
            //'t';
            case ParseConstants.T_:
            //'u';
            case ParseConstants.U_:
            //'v';
            case ParseConstants.V_:
            //'w';
            case ParseConstants.W_:
            //'x';
            case ParseConstants.X_:
            //'y';
            case ParseConstants.Y_:
            case //'z';
            ParseConstants.Z_:
                continue;
            case ParseConstants.ETX:
                break loop;
            case ParseConstants.DOT:
                break loop;
            case ParseConstants.INDEX_BRACKET_START_TOKEN:
                final int endIndex = source.getIndex();
                tokens.add(new Token(startIndex, endIndex, TokenTypes.PATH_KEY_TOKEN));
                parseIndexOrKey(source, (char) source.next(), tokens);
                return;
            default:
                throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
        }
    }
    final int endIndex = source.getIndex();
    tokens.add(new Token(startIndex, endIndex, TokenTypes.PATH_KEY_TOKEN));
}

The parseKeyName method in the PathParser class is responsible for parsing a key name from a character source. It follows these steps:

  1. Start a loop that continues indefinitely.
  2. Get the next character from the character source.
  3. Check the value of the character using a switch statement.
  4. If the character is one of the uppercase letters from 'A' to 'Z' or the lowercase letter 'a', 'b', 'c', ..., 'z', continue to the next iteration of the loop.
  5. If the character is the end-of-text character (represented by ParseConstants.ETX) or a dot character (represented by ParseConstants.DOT), break out of the loop.
  6. If the character is an opening square bracket (represented by ParseConstants.INDEX_BRACKET_START_TOKEN), create a token for the current key name, add it to the token list, and call the parseIndexOrKey method to parse the index or key that follows the square bracket.
  7. Return from the method.
  8. If none of the above cases match, throw an IllegalStateException with an error message indicating that the character is not recognized.
  9. After breaking out of the loop, create a token for the current key name, add it to the token list, and return from the method.

The parseKeyName method is responsible for parsing a key name from a given source of characters. It iterates over the characters in the source and checks each character against a set of predefined constants representing valid key name characters. If the character is a valid key name character, the iteration continues. If the character is a delimiter, like a dot or an index bracket start token, the method adds a token representing the key name to a list of tokens and calls another method to parse the index or key following the delimiter. If the character is neither a valid key name character nor a delimiter, an exception is thrown indicating that the character is not understood. Once the iteration is complete, the method adds a final key name token to the list and returns.

sequence diagram

private void parseIndex(CharSource source, int startIndex, TokenList tokens, char ch)

private void parseIndex(CharSource source, int startIndex, TokenList tokens, char ch) {
    loop: while (true) {
        ch = (char) source.next();
        switch(ch) {
            case ParseConstants.NUM_0:
            case ParseConstants.NUM_1:
            case ParseConstants.NUM_2:
            case ParseConstants.NUM_3:
            case ParseConstants.NUM_4:
            case ParseConstants.NUM_5:
            case ParseConstants.NUM_6:
            case ParseConstants.NUM_7:
            case ParseConstants.NUM_8:
            case ParseConstants.NUM_9:
                break;
            case ParseConstants.INDEX_BRACKET_END_TOKEN:
                break loop;
            case ParseConstants.ETX:
                throw new IllegalStateException("reached end");
            default:
                throw new IllegalStateException("Unable to understand char " + ch + " index " + source.getIndex());
        }
    }
    final int endIndex = source.getIndex();
    tokens.add(new Token(startIndex, endIndex, TokenTypes.PATH_INDEX_TOKEN));
}

Method: parseIndex

This method is defined in the io.nats.jparse.path.PathParser class and is used to parse an index from a CharSource object.

Steps:

  1. The method starts by entering an infinite loop using a while (true) statement. This loop will break only when a specific condition is met.

  2. Inside the loop, the next character from the CharSource object is retrieved using the source.next() method and assigned to the variable ch.

  3. A switch statement is used to check the value of ch and perform different actions based on its value. The following cases are handled:

    • If ch is '0', '1', '2', '3', '4', '5', '6', '7', '8', or '9', no action is taken.

    • If ch is ']', the loop is broken using the break loop; statement.

    • If ch is ParseConstants.ETX, an IllegalStateException is thrown with the message "reached end".

    • If none of the above cases match, an IllegalStateException is thrown with the message "Unable to understand char " + ch + " index " + source.getIndex()".

  4. After the loop is exited, the final index of the CharSource object is retrieved using the source.getIndex() method and assigned to the variable endIndex.

  5. A new Token object is created with the startIndex, endIndex, and TokenTypes.PATH_INDEX_TOKEN. This token is then added to the tokens list using the tokens.add() method.

Note: The TokenList and Token objects mentioned here are assumed to be defined elsewhere and are not shown in the given code snippet.

The parseIndex method in the io.nats.jparse.path.PathParser class is used to parse an index from a character source (CharSource).

The method iterates through the characters in the source until it reaches a specific condition. It checks if each character is a digit from 0 to 9 and if so, continues to the next character. If the character is the end token for an index bracket, it breaks out of the loop. If the character is the ETX (end of text) constant, it throws an IllegalStateException with the message "reached end". If none of these conditions are met, it throws an IllegalStateException with the message "Unable to understand char [char] index [index]".

Once the loop is broken, the method retrieves the current index from the source and adds a new token to the tokens list. This token represents the parsed index and is defined by the TOKEN_INDEX constant.

Overall, the parseIndex method is responsible for parsing an index from a character source and adding it as a token to a list.

sequence diagram

KeyPathNode

The KeyPathNode class is a public class that represents a key element of a JSON Path expression. It implements the ScalarNode and PathElement interfaces. It provides methods for determining the type of element and for converting it to a key representation.

IndexPathNode

The IndexPathNode class is a public class that represents an index element of a JSON Path expression. It is a node in the parse tree that implements the ScalarNode, CharSequence, and PathElement interfaces.

// Overridden Number class methods with their appropriate Javadoc comments

/**

  • Returns the value of this node as an int.
  • @return The int value represented by this object. */ @Override public int intValue()
// Overridden Number class methods with their appropriate Javadoc comments
/**
 * Returns the value of this node as an int.
 *
 * @return The int value represented by this object.
 */
@Override
public int intValue() {
    return source.getInt(token.startIndex, token.endIndex);
}

The intValue method, which is defined in the io.nats.jparse.path.IndexPathNode class, is responsible for returning the value of the node as an int. Here is a step-by-step description of what the method is doing based on its body:

  1. The method is overridden with appropriate Javadoc comments to explain its functionality.

  2. The method signature specifies that the return type is int.

  3. Inside the method body, a call to the source.getInt() method is made, passing two parameters: token.startIndex and token.endIndex. This indicates that the source object has a method called getInt() that accepts the start and end indexes of the token.

  4. The return value of the source.getInt() method is used as the return value of the intValue() method.

Overall, the intValue() method is retrieving the int value from the source object by passing the start and end indexes of the token and returning that value as the result of the method call.

The intValue method in the IndexPathNode class, located in the io.nats.jparse.path package, returns the value of this node as an integer. It retrieves the integer value from the source object, using the start and end indices of the token associated with this node.

sequence diagram

io.nats.jparse.node

class diagram

JsonTestUtils

The JsonTestUtils class is a public class that provides utilities for testing JSON data in software engineering. It provides convenient methods for comparing and validating JSON objects, making it easier to write test cases for code that deals with JSON data.

NullNode

The NullNode class represents a null value node in a tree structure. It is a concrete implementation of the ScalarNode interface.

@Override

public char charAt(int index)

@Override
public char charAt(int index) {
    switch(index) {
        case 0:
            return 'n';
        case 1:
            return 'u';
        case 2:
        case 3:
            return 'l';
        default:
            throw new IllegalStateException();
    }
}

Method: charAt

This method is part of the NullNode class in the io.nats.jparse.node package. It is an overridden implementation of the charAt method from the CharSequence interface.

Description

The charAt method returns the character at the specified index of the NullNode object.

Parameters

  • index (integer): The index of the character to retrieve.

Return Value

  • char: The character at the specified index.

Steps

  1. Start the method execution.
  2. Check the value of the index parameter using a switch statement.
  3. If index is 0, return the character 'n'.
  4. If index is 1, return the character 'u'.
  5. If index is 2 or 3, return the character 'l'.
  6. If none of the above cases match, throw an IllegalStateException.
  7. End the method execution.

Note: This method only handles the cases 0, 1, 2, 3. Any other value will result in an exception being thrown.

sequence diagram

NumberNode

The NumberNode class represents a numeric node in a tree structure. It can store integer, long, float, and double values. This class implements the ScalarNode and CharSequence interfaces.

@Override

public Object value()

@Override
public Object value() {
    if (isInteger()) {
        return intValue();
    } else if (isLong()) {
        return longValue();
    } else {
        return this.doubleValue();
    }
}

The method value() in the class io.nats.jparse.node.NumberNode returns the value of the number node. Here is a step-by-step description of what this method does based on its body:

  1. Check if the number node is an integer, by calling the method isInteger().
  2. If the number node is an integer, call the method intValue() to get the integer value and return it as an Object.
  3. If the number node is not an integer, check if it is a long integer, by calling the method isLong().
  4. If the number node is a long integer, call the method longValue() to get the long integer value and return it as an Object.
  5. If the number node is neither an integer nor a long integer, call the method doubleValue() to get the double value and return it as an Object.

The purpose of this method is to provide a consistent way of accessing the value of a number node, regardless of its specific data type (integer, long, or double).

sequence diagram

@Override

public char charAt(int index)

@Override
public char charAt(int index) {
    if (index > length()) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    return source.getChartAt(token.startIndex + index);
}

The charAt method in the NumberNode class is designed to retrieve the character at a specified index in the source string. Here is a step-by-step description of what the method does:

  1. The method overrides the charAt method defined in the CharSequence interface.

  2. It takes an int parameter called index, which represents the desired index of the character to be retrieved.

  3. The method first checks if the index is greater than the length of the source string.

  4. If the index is greater than the length of the source string, the method throws an ArrayIndexOutOfBoundsException with the provided index.

  5. If the index is valid (i.e., within the bounds of the source string), the method retrieves the character at the specified index.

  6. The character is obtained by calling the getChartAt method on the source object, passing the startIndex of the token plus the index as arguments.

  7. Finally, the retrieved character is returned as the result of the method.

In summary, the charAt method in the NumberNode class ensures that the specified index is valid, and then retrieves and returns the character at that index from the source string.

sequence diagram

@Override

public CharSequence subSequence(int start, int end)

@Override
public CharSequence subSequence(int start, int end) {
    if (end > length()) {
        throw new IndexOutOfBoundsException();
    }
    return source.getCharSequence(start + token.startIndex, end + token.startIndex);
}

The subSequence method, which is defined in the NumberNode class in the io.nats.jparse.node package, allows us to get a portion of the character sequence contained in the NumberNode object.

Here is a step-by-step description of what the method does based on its body:

  1. The method is annotated with @Override, indicating that it overrides a method from the superclass or an interface. In this case, it overrides the subSequence method from the CharSequence interface.

  2. The method has two input parameters, start and end, which indicate the range of indices (inclusive) for the desired subsequence.

  3. The first line of the method's body checks if the end parameter is greater than the length of the character sequence. If it is, an IndexOutOfBoundsException is thrown.

  4. If the end parameter is within the valid range, the method proceeds to the next line.

  5. The method then calls the getCharSequence method on the source object with the calculated start and end indices. The start index is obtained by adding the start parameter to the startIndex of the token object, and the end index is obtained by adding the end parameter to the startIndex of the token object.

  6. Finally, the getCharSequence method returns the requested subsequence of characters.

That's the step-by-step description of the subSequence method in the NumberNode class.

sequence diagram

public boolean isInteger()

public boolean isInteger() {
    switch(elementType) {
        case INT:
            return source.isInteger(this.token.startIndex, this.token.endIndex);
        default:
            return false;
    }
}

The method isInteger() located in the io.nats.jparse.node.NumberNode class is used to determine if the given number is an integer. Here is a step-by-step description of how this method works based on its implementation:

  1. The method isInteger() takes no parameters and returns a boolean value indicating whether the number is an integer or not.

  2. The method starts by checking the value of the elementType variable, which is an enumeration representing the type of the number.

  3. If the elementType is INT, it means that the number is an integer.

  4. In the case when the elementType is INT, the isInteger() method calls the source.isInteger() method passing two parameters this.token.startIndex and this.token.endIndex. These parameters represent the start and end indices of the token associated with this number.

  5. The source.isInteger() method is expected to be implemented in another class and is responsible for determining if the number between the given indices is an integer or not.

  6. If the elementType is not INT, the method returns false, indicating that the number is not an integer.

  7. The isInteger() method provides a way to validate if the given number is an integer based on its type elementType.

sequence diagram

public boolean isLong()

public boolean isLong() {
    switch(elementType) {
        case INT:
            return !source.isInteger(this.token.startIndex, this.token.endIndex);
        default:
            return false;
    }
}

The method isLong() in the NumberNode class, located in the io.nats.jparse.node package, is used to determine whether the number represented by the node is a long type or not. Below is a step-by-step description of how this method works based on its body:

  1. The method is defined as a public boolean method, meaning it returns a boolean value and can be accessed from outside the class.

  2. The method starts with a switch statement that evaluates the value of the elementType variable. The elementType variable is presumably an enumeration or constant that represents the type of the element.

  3. Inside the switch statement, there is a case for the INT type. This means that if the elementType is equal to INT, the following code will be executed.

  4. Within the case INT, there is a return statement that calls a method isInteger() on the source object. This method is likely defined in another class or utility and takes two parameters: this.token.startIndex and this.token.endIndex. The purpose of this method is to check whether the token, which represents a portion of the input source, is an integer.

  5. The ! operator is applied to the result of the isInteger() method. This means that if the token is NOT an integer, the result will be true, indicating that the number is a long type.

  6. If the elementType is not equal to INT, the default case is executed. In this case, the method returns false, indicating that the number is not a long type.

To summarize, the isLong() method checks the elementType of the node and if it is an INT, it verifies whether the corresponding token represents an integer. If the token is not an integer, the method returns true, indicating that the number is a long type. Otherwise, if the elementType is not INT, the method returns false.

sequence diagram

StringNode

The StringNode class in the JParse library is a specialized ScalarNode that represents a string value. It contains information about the token, source, start and end indices, and whether the string should be encoded by default. This class provides methods to access the string value, perform operations on the string such as getting the length and retrieving characters, and creating substrings. StringNode implements the CharSequence interface and overrides the equals and hashCode methods for proper comparison and hashing of the string value.

For more information, see the documentation for the related classes: ScalarNode (from the io.nats.jparse.node package) and CharSequence (from the java.lang package).

RootNode

The RootNode class is a concrete implementation of the CollectionNode interface. It represents the root node of a tree structure. This class serves as the entry point for accessing and manipulating the contents of the tree. The root node can be either an object node or an array node.

@Override

public List<List> childrenTokens()

@Override
public List<List<Token>> childrenTokens() {
    switch(rootToken.type) {
        case OBJECT_TOKEN:
            return getObjectNode().childrenTokens();
        case ARRAY_TOKEN:
            return getArrayNode().childrenTokens();
        default:
            return doGetChildrenTokens();
    }
}

The childrenTokens() method in the io.nats.jparse.node.RootNode class is defined as follows:

  1. The method is annotated with @Override, indicating that it overrides a method from a superclass or interface.

  2. The method returns a List<List<Token>>, which is a list of lists of Token objects.

  3. The method utilizes a switch statement based on the type of the rootToken variable.

  4. If the type of rootToken is OBJECT_TOKEN, the method calls the childrenTokens() method on the ObjectNode instance returned by the getObjectNode() method. The returned List<List<Token>> is then returned by the method.

  5. If the type of rootToken is ARRAY_TOKEN, the method calls the childrenTokens() method on the ArrayNode instance returned by the getArrayNode() method. The returned List<List<Token>> is then returned by the method.

  6. If the type of rootToken does not match any of the above cases, the method calls the doGetChildrenTokens() method. This method is not explicitly provided in the given snippet, but it is assumed to return a List<List<Token>>. The returned value is then returned by the childrenTokens() method.

Note: Without the implementation of the getObjectNode(), getArrayNode(), and doGetChildrenTokens() methods, it is not possible to provide a more detailed description of their functionality and behavior.

sequence diagram

ObjectNode

The ObjectNode class represents an object node in a tree structure.

It extends the AbstractMap class and implements the CollectionNode interface.

Object nodes are used to store key-value pairs, where the keys are CharSequences and the values are nodes in the tree structure.

@Override

public List<List> childrenTokens()

@Override
public List<List<Token>> childrenTokens() {
    if (childrenTokens == null) {
        childrenTokens = NodeUtils.getChildrenTokens(tokens);
    }
    return childrenTokens;
}

The method childrenTokens() in the ObjectNode class is used to retrieve the children tokens of the current object node. Here is a step-by-step description of what this method does:

  1. The method is annotated with @Override, indicating that it overrides the implementation of the same method in a superclass or interface.

  2. The return type of the method is List<List<Token>>, which means it returns a list of lists of Token objects.

  3. Inside the method, there is an if statement to check if the childrenTokens variable is null. This variable is an instance variable of the ObjectNode class.

  4. If childrenTokens is null, the method proceeds to the next line. Otherwise, it skips to the return statement.

  5. On the next line, the method calls the getChildrenTokens() method from the NodeUtils class and passes the tokens parameter to it. The tokens parameter is an instance variable of the ObjectNode class.

  6. The return value from the getChildrenTokens() method is assigned to the childrenTokens variable.

  7. Finally, the method returns the value of the childrenTokens variable.

In summary, the childrenTokens() method is used to retrieve the children tokens of the current object node. It first checks if the childrenTokens variable is null. If it is null, it calls a helper method to fetch the children tokens and assigns the result to the childrenTokens variable. Then, it returns the value of the childrenTokens variable.

sequence diagram

@Override

public Set<Entry<CharSequence, Node>> entrySet()

@Override
public Set<Entry<CharSequence, Node>> entrySet() {
    return new AbstractSet<Entry<CharSequence, Node>>() {

        /**
         * Checks if the object node contains the specified entry.
         *
         * @param o the entry to check for existence
         * @return {@code true} if the object node contains the entry, {@code false} otherwise
         */
        @Override
        public boolean contains(Object o) {
            return keys().contains(o);
        }

        /**
         * Returns an iterator over the entries in the object node.
         *
         * @return an iterator over the entries in the object node
         */
        @Override
        public Iterator<Entry<CharSequence, Node>> iterator() {
            final Iterator<CharSequence> iterator = keys().iterator();
            return new Iterator<Entry<CharSequence, Node>>() {

                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                @Override
                public Entry<CharSequence, Node> next() {
                    final CharSequence nextKey = iterator.next().toString();
                    return new Entry<CharSequence, Node>() {

                        @Override
                        public CharSequence getKey() {
                            return nextKey;
                        }

                        @Override
                        public Node getValue() {
                            return lookupElement(nextKey);
                        }

                        @Override
                        public Node setValue(Node value) {
                            throw new UnsupportedOperationException();
                        }
                    };
                }
            };
        }

        /**
         * Returns the number of entries in the object node.
         *
         * @return the number of entries in the object node
         */
        @Override
        public int size() {
            return keys().size();
        }
    };
}

The entrySet method is defined in class io.nats.jparse.node.ObjectNode and is overridden from its superclass. Here is a step-by-step description of what this method is doing:

  1. The entrySet method is marked with the @Override annotation, indicating that it is overriding a method from the superclass.

  2. The method returns a new instance of AbstractSet<Entry<CharSequence, Node>>. This means that it returns a set of map entries, where each entry consists of a CharSequence key and a Node value.

  3. Inside the AbstractSet constructor, several methods are overridden and implemented. These methods provide functionality for checking if the object node contains a specific entry, iterating over the entries, and getting the size of the entry set.

  4. The contains method takes an object as a parameter and checks if the object node contains the specified entry. It delegates the check to the keys method, which returns a set of keys in the object node, and then calls the contains method of that set.

  5. The iterator method returns an iterator over the entries in the object node. It first obtains an iterator over the keys in the object node by calling the keys method. Then, it returns a new iterator object that implements the Iterator<Entry<CharSequence, Node>> interface. Each iteration, it converts the next key into a CharSequence and creates a new entry object with the key and the value obtained by calling the lookupElement method.

  6. The size method returns the number of entries in the object node. It delegates the size calculation to the keys method, which returns a set of keys in the object node, and then calls the size method of that set.

Overall, the entrySet method provides a way to work with the entries of the object node as a set, allowing operations such as checking if an entry exists, iterating over the entries, and getting the size of the entry set.

sequence diagram

private Node lookupElement(final CharSequence key)

private Node lookupElement(final CharSequence key) {
    if (elementMap == null) {
        elementMap = new HashMap<>();
    }
    Node node = elementMap.get(key);
    if (node == null) {
        List<List<Token>> childrenTokens = childrenTokens();
        for (int index = 0; index < childrenTokens.size(); index += 2) {
            List<Token> itemKey = childrenTokens.get(index);
            if (doesMatchKey(itemKey, key)) {
                node = NodeUtils.createNodeForObject(childrenTokens.get(index + 1), source, objectsKeysCanBeEncoded);
                elementMap.put(key, node);
                break;
            }
        }
    }
    return node;
}

Method lookupElement

The lookupElement method is defined in the ObjectNode class and is used to look up an element based on a given key.

Method Signature

private Node lookupElement(final CharSequence key)

Description

  1. Check if the elementMap attribute is null.

    • If it is null, create a new HashMap and assign it to elementMap.
  2. Retrieve the node associated with the given key from the elementMap.

    • Store the result in the node variable.
  3. Check if the retrieved node is null.

    • If it is null, proceed with the following steps.
    • If it is not null, skip the following steps and return the node.
  4. Retrieve the childrenTokens by calling the childrenTokens() method.

  5. Iterate over the childrenTokens list in increments of 2.

    • For each pair of tokens (item key and value):
      • Store the item key in the itemKey variable.
  6. Check if the item key matches the given key by calling the doesMatchKey method.

    • If it matches, proceed with the following steps.
    • If it does not match, move to the next pair of tokens.
  7. Call the createNodeForObject method from the NodeUtils class, passing the value tokens, source, and objectsKeysCanBeEncoded arguments.

    • Store the returned node in the node variable.
  8. Add the key and node pair to the elementMap.

    • This stores the key-value mapping for future lookups.
  9. Exit the loop.

  10. Return the node.

Return Value

The method returns the node associated with the given key.

sequence diagram

private boolean doesMatchKey(final List itemKey, final CharSequence key)

private boolean doesMatchKey(final List<Token> itemKey, final CharSequence key) {
    final Token keyToken = itemKey.get(1);
    if (keyToken.type == TokenTypes.STRING_TOKEN) {
        if (keyToken.length() != key.length()) {
            return false;
        }
        if (objectsKeysCanBeEncoded) {
            final StringNode stringNode = new StringNode(keyToken, source, objectsKeysCanBeEncoded);
            final String string = stringNode.toString();
            for (int index = 0; index < key.length(); index++) {
                if (string.charAt(index) != key.charAt(index)) {
                    return false;
                }
            }
            return true;
        } else {
            return source.matchChars(keyToken.startIndex, keyToken.endIndex, key);
        }
    }
    return false;
}

Method: doesMatchKey

Description:

The doesMatchKey method is a private method defined in the io.nats.jparse.node.ObjectNode class. This method is used to check whether a given key matches a specified item key.

Parameters:

  • itemKey (Type: List<Token>): A list of tokens representing the item key.
  • key (Type: CharSequence): The key to be checked for a match.

Steps:

  1. Get the token at index 1 from the itemKey list, and assign it to the keyToken variable.
  2. Check the type of the keyToken:
    • If the type is TokenTypes.STRING_TOKEN, proceed with the following steps:
      • Check if the length of keyToken is equal to the length of the key:
        • If not equal, return false.
      • Check if objectsKeysCanBeEncoded is true:
        • If true, execute the following sub-steps:
          • Create a new StringNode object named stringNode with the keyToken, source, and objectsKeysCanBeEncoded as parameters.
          • Convert the stringNode to a String and assign it to the string variable.
          • Iterate over each character in the key using a for loop:
            • Compare the character at the current index in the string with the character at the current index in the key:
              • If they are not equal, return false.
          • If all characters match, return true.
        • If false, execute the following sub-step:
          • Call the matchChars method on the source object, passing keyToken.startIndex, keyToken.endIndex, and key as parameters.
          • Return the result of the matchChars method.
    • If the type is not TokenTypes.STRING_TOKEN, return false.

Return Value:

  • Type: boolean
  • If the key matches the specified item key, true is returned. Otherwise, false is returned.

sequence diagram

private List keys()

private List<CharSequence> keys() {
    if (keys == null) {
        List<List<Token>> childrenTokens = childrenTokens();
        keys = new ArrayList<>(childrenTokens.size() / 2);
        for (int index = 0; index < childrenTokens.size(); index += 2) {
            List<Token> itemKey = childrenTokens.get(index);
            Token keyToken = itemKey.get(1);
            switch(keyToken.type) {
                case TokenTypes.STRING_TOKEN:
                    final StringNode element = new StringNode(keyToken, source, objectsKeysCanBeEncoded);
                    keys.add(element);
                    break;
                default:
                    throw new IllegalStateException("Only String are allowed for keys " + TokenTypes.getTypeName(keyToken.type));
            }
            ;
        }
    }
    return keys;
}

The keys() method in the ObjectNode class is responsible for retrieving the keys of the object. Here is a step-by-step description of what the method is doing based on its body:

  1. Check if the keys list is null.
  2. If the keys list is null, retrieve the children tokens of the object and store them in the childrenTokens list.
  3. Create a new ArrayList with an initial capacity equal to half of the size of the childrenTokens list, and assign it to the keys list.
  4. Use a for loop to iterate over the childrenTokens list, incrementing the index by 2 each iteration.
  5. Inside the loop, retrieve the itemKey list at the current index from the childrenTokens list.
  6. Get the second token from the itemKey list and assign it to the keyToken variable.
  7. Use a switch statement to check the type of the keyToken.
  8. If the type of the keyToken is STRING_TOKEN, create a new StringNode object using the keyToken, source, and objectsKeysCanBeEncoded variables.
  9. Add the StringNode object to the keys list.
  10. If the type of the keyToken is not STRING_TOKEN, throw an IllegalStateException with a message indicating that only strings are allowed for keys.
  11. After the loop ends, return the keys list.

This method retrieves the keys of an object by iterating over the children tokens and extracting the second token from each key. If the token is of type STRING_TOKEN, it creates a StringNode object with the token and adds it to the keys list. If the token's type is different, it throws an exception. Finally, it returns the keys list.

sequence diagram

ArrayNode

The ArrayNode class represents an array node in a tree structure. It extends the AbstractList class and implements the CollectionNode interface.

@Override

public List<List> childrenTokens()

@Override
public List<List<Token>> childrenTokens() {
    if (childrenTokens == null) {
        childrenTokens = NodeUtils.getChildrenTokens(tokens);
    }
    return childrenTokens;
}

The childrenTokens method in the ArrayNode class, which is defined in the io.nats.jparse.node package, performs the following steps:

  1. The method overrides the childrenTokens method from the superclass (Node), indicating that it provides a specific implementation for this method.

  2. The method has a return type of List<List<Token>>, which means it returns a list of lists of Token objects.

  3. When the method is invoked, it first checks if the variable childrenTokens is null.

  4. If childrenTokens is null, it means that the list of children tokens has not been initialized yet.

  5. In that case, the method invokes the static getChildrenTokens method from the NodeUtils class, passing the tokens list as an argument.

  6. The getChildrenTokens method is responsible for extracting and organizing the tokens that represent the children of the current node. It likely iterates over the tokens list, identifies the children tokens, and creates a nested list structure to represent the children tokens of each child node.

  7. After getChildrenTokens returns, the method assigns the returned value to the childrenTokens variable, effectively caching the list of children tokens for future invocations.

  8. Finally, the method returns the value of the childrenTokens variable, which now contains the list of children tokens for the current ArrayNode.

Overall, the childrenTokens method provides a way to retrieve the organized children tokens of an ArrayNode. It ensures that the children tokens are computed only once and caches the result for future use, improving performance when invoking this method multiple times.

Node[] elements()

Node[] elements() {
    if (elements == null) {
        elements = new Node[childrenTokens().size()];
    }
    return elements;
}

Method Description: elements()

This method is defined in the ArrayNode class in the io.nats.jparse.node package. It returns an array of Node objects. The array represents the elements of the ArrayNode object.

Steps:

  1. Check if the elements array is null.
  2. If the elements array is null, create a new array of Node objects with the size equal to the number of children tokens of the ArrayNode object. This ensures that the elements array is properly initialized with the correct size.
  3. Return the elements array.

Please note that if the elements array has already been initialized, Step 2 is skipped as it is not necessary to recreate the array. The main purpose of this method is to lazily initialize the elements array when it is first accessed.

sequence diagram

public List map(Function<Node, ? extends R> mapper)

public <R> List<R> map(Function<Node, ? extends R> mapper) {
    List<R> list = new ArrayList<>(this.size());
    Node[] elements = elements();
    for (int i = 0; i < elements.length; i++) {
        Node element = elements[i];
        if (element == null) {
            element = getNodeAt(i);
            elements[i] = element;
        }
        list.add(mapper.apply(element));
    }
    return list;
}

The map method in the ArrayNode class is performing the following steps:

  1. It takes a Function object called mapper as a parameter. The mapper function is used to transform each element of the array.

  2. It creates an ArrayList object called list with an initial capacity equal to the size of the array. This is done to optimize the performance by reducing the number of reallocations.

  3. It retrieves an array of Node objects called elements from the elements() method.

  4. It iterates over each element in the elements array using a for loop.

  5. Within the loop, it retrieves the current element at index i and assigns it to a variable called element. If the element is null, it calls the getNodeAt method to fetch the element at index i and assigns it back to element. This is done to ensure that each element is not null before applying the mapper function.

  6. It applies the mapper function to the element using the apply method, and adds the transformed result to the list using the add method.

  7. After processing all the elements, it returns the list containing the transformed elements.

Note: The mapper function can be any operation that you want to perform on each element of the array. It could be a simple transformation, filtering, or any other custom operation based on your requirements.

sequence diagram

public Optional findObjectNode(Predicate predicate)

public Optional<ObjectNode> findObjectNode(Predicate<ObjectNode> predicate) {
    final Node[] elements = elements();
    ObjectNode node = null;
    for (int i = 0; i < elements.length; i++) {
        Node element = elements[i];
        if (element == null) {
            element = getNodeAt(i);
        }
        if (element.type() == NodeType.OBJECT) {
            ObjectNode objectNode = element.asCollection().asObject();
            if (predicate.test(objectNode)) {
                node = objectNode;
                break;
            }
        }
    }
    return Optional.ofNullable(node);
}

findObjectNode(Predicate<ObjectNode> predicate)

This method is a member of the ArrayNode class located in the io.nats.jparse.node package. It returns an Optional<ObjectNode> based on the given Predicate<ObjectNode>.

The Predicate is used to test each ObjectNode in the array. It is passed as a parameter to the method and allows the caller to define the test condition.

Here is a step-by-step description of what the method does:

  1. Get array elements: Retrieve the array elements using the elements() method, which returns an array of Node objects.

  2. Iterate over array elements: Iterate over each element in the array using a standard for loop.

  3. Handle null elements: Check if the current array element is null. If so, call the getNodeAt(i) method to retrieve the element at the given index.

  4. Check element type: Determine if the current element is of type OBJECT. This is done by calling the type() method on the Node object and comparing the result with NodeType.OBJECT.

  5. Convert element to ObjectNode: If the element is an OBJECT type, it is converted to an ObjectNode by calling the asCollection().asObject() method.

  6. Test ObjectNode with the predicate: Apply the given predicate to the ObjectNode using the test() method. If the test condition is satisfied, the ObjectNode is considered a match.

  7. Set matched ObjectNode: If a match is found, assign the matched ObjectNode to the node variable and exit the loop.

  8. Return Optional<ObjectNode>: Wrap the matched ObjectNode in an Optional object and return it. If no match was found, null is returned as an Optional by using Optional.ofNullable().

Note: The method stops once a matching ObjectNode is found and returns it immediately, without iterating over the remaining array elements.

sequence diagram

public Optional find(Predicate predicate)

public Optional<Node> find(Predicate<Node> predicate) {
    Node[] elements = elements();
    Node node = null;
    for (int i = 0; i < elements.length; i++) {
        Node element = elements[i];
        if (element == null) {
            element = getNodeAt(i);
        }
        if (predicate.test(element)) {
            node = element;
            break;
        }
    }
    return Optional.ofNullable(node);
}

find Method

The find method is defined in the io.nats.jparse.node.ArrayNode class. It takes a Predicate<Node> as input and returns an optional Node.

Method Signature

public Optional<Node> find(Predicate<Node> predicate)

Method Description

The find method searches for a Node in the ArrayNode that matches the given predicate. It iterates through the elements of the array and calls the test method of the predicate on each element. If a matching element is found, it is assigned to the node variable and the loop is exited. Finally, the method returns an Optional containing the node or an empty Optional if no matching node was found.

Parameters

  • predicate: A Predicate<Node> used to test each element of the array. It is a functional interface that defines a single abstract method test which takes a Node and returns a boolean indicating whether the given node matches the required criteria.

Local Variables

  • elements: An array of Node elements obtained from the elements method of the ArrayNode class.
  • node: A Node variable used to store the matching element. It is initialized as null and later assigned the matching element if found.

Loop

The method uses a for loop to iterate through the elements of the array.

  1. It initializes the loop counter i to 0.
  2. The loop runs as long as i is less than the length of the elements array.
  3. In each iteration, the current element at index i is assigned to the element variable.
  4. If the element is null, the getNodeAt method is called to obtain the element at index i.
  5. The test method of the predicate is called with the element as input. If it returns true, the node variable is assigned the value of element and the loop is exited using the break statement.
  6. After the loop, the method returns an Optional containing the value of node.

Return Value

The method returns an Optional<Node>, which may contain the matching Node if found, or an empty Optional if no matching node was found.

sequence diagram

public List filterObjects(Predicate predicate)

public List<ObjectNode> filterObjects(Predicate<ObjectNode> predicate) {
    Node[] elements = elements();
    final int length = elements.length;
    final List<ObjectNode> arrayList = new ArrayList<>(length / 2);
    for (int i = 0; i < length; i++) {
        Node element = elements[i];
        if (element == null) {
            element = getNodeAt(i);
        }
        if (element.type() == NodeType.OBJECT) {
            ObjectNode objectNode = element.asCollection().asObject();
            if (predicate.test(objectNode)) {
                arrayList.add(objectNode);
            }
        }
    }
    return arrayList;
}

The filterObjects method in the class io.nats.jparse.node.ArrayNode is performing the following steps:

  1. The method takes in a Predicate<ObjectNode> as a parameter, which allows the caller to specify a condition for filtering the objects.

  2. It retrieves the elements of the array that the ArrayNode represents by calling the elements() method.

  3. It initializes a list called arrayList to store the filtered object nodes. The initial capacity of the ArrayList is set to length / 2, where length is the length of the elements array.

  4. It then iterates over each element in the array using a for loop.

  5. Inside the loop, it checks if the current element is null. If it is null, it calls the getNodeAt(i) method to retrieve the element at that index.

  6. It then checks the type of the element using the type() method. If the type is NodeType.OBJECT, it proceeds with the following steps.

  7. It converts the element to an ObjectNode by calling the asCollection().asObject() method.

  8. It applies the predicate to the ObjectNode by calling the test(objectNode) method on the predicate. If the predicate returns true, the ObjectNode is added to the arrayList using the add(objectNode) method.

  9. After the loop ends, the method returns the arrayList containing the filtered ObjectNode objects.

This method allows you to filter an array of objects and receive a list of object nodes that satisfy the specified predicate.

sequence diagram

public List filter(Predicate predicate)

public List<Node> filter(Predicate<Node> predicate) {
    Node[] elements = elements();
    final int length = elements.length;
    final List<Node> arrayList = new ArrayList<>(length / 2);
    for (int i = 0; i < length; i++) {
        Node element = elements[i];
        if (element == null) {
            element = getNodeAt(i);
        }
        if (predicate.test(element)) {
            arrayList.add(element);
        }
    }
    return arrayList;
}

The filter method in the ArrayNode class is used to filter the elements of the node array based on a given predicate. Here is a step-by-step description of what this method does:

  1. Declare a local variable elements and assign the result of the elements() method call. This method returns an array of Node objects.

  2. Get the length of the elements array and store it in a variable called length.

  3. Create a new ArrayList called arrayList with an initial capacity of length / 2.

  4. Start a loop that iterates over the elements of the elements array. The loop index is i, initialized to 0.

  5. Get the Node object at index i and assign it to a local variable called element. If the element is null, call the getNodeAt(i) method to retrieve a non-null element.

  6. Check if the element satisfies the given predicate. The predicate is passed as a parameter to the filter method and is of type Predicate<Node>. The test method of the Predicate interface is used to evaluate the element against the given predicate.

  7. If the element passes the predicate test, add it to the arrayList using the add method.

  8. Continue the loop until all elements in the elements array have been processed.

  9. Return the arrayList containing the elements that passed the predicate test.

By using the filter method with a custom predicate, you can easily select and return a subset of elements from the ArrayNode based on a specific condition or criteria.

sequence diagram

BooleanNode

The BooleanNode class represents a boolean value node in a tree structure. It is part of a broader software engineering implementation where it serves as a scalar node.

@Override

public char charAt(int index)

@Override
public char charAt(int index) {
    if (value) {
        switch(index) {
            case 0:
                return 't';
            case 1:
                return 'r';
            case 2:
                return 'u';
            case 3:
                return 'e';
            default:
                throw new IllegalStateException();
        }
    } else {
        switch(index) {
            case 0:
                return 'f';
            case 1:
                return 'a';
            case 2:
                return 'l';
            case 3:
                return 's';
            case 4:
                return 'e';
            default:
                throw new IllegalStateException();
        }
    }
}

Method charAt in class io.nats.jparse.node.BooleanNode

This method is an overridden implementation of the charAt method from the CharSequence interface. The method takes an integer index as input and returns the character at that index position from the boolean value stored in the value variable of the BooleanNode object.

Method Signature

public char charAt(int index) 

Method Body

@Override
public char charAt(int index) {
    if (value) { // if the boolean value is true
        switch(index) {
            case 0:
                return 't'; // returns 't' when index is 0
            case 1:
                return 'r'; // returns 'r' when index is 1
            case 2:
                return 'u'; // returns 'u' when index is 2
            case 3:
                return 'e'; // returns 'e' when index is 3
            default:
                throw new IllegalStateException(); // throws an exception for any other index
        }
    } else { // if the boolean value is false
        switch(index) {
            case 0:
                return 'f'; // returns 'f' when index is 0
            case 1:
                return 'a'; // returns 'a' when index is 1
            case 2:
                return 'l'; // returns 'l' when index is 2
            case 3:
                return 's'; // returns 's' when index is 3
            case 4:
                return 'e'; // returns 'e' when index is 4
            default:
                throw new IllegalStateException(); // throws an exception for any other index
        }
    }
}

In summary, the charAt method in the BooleanNode class checks the value of the boolean variable value and returns a character based on the index value. If the boolean value is true, it returns 't' for index 0, 'r' for index 1, 'u' for index 2, and 'e' for index 3. If the boolean value is false, it returns 'f' for index 0, 'a' for index 1, 'l' for index 2, 's' for index 3, and 'e' for index 4. Any other index value throws an IllegalStateException.

sequence diagram

io.nats.jparse.node.support

PathUtils

The PathUtils class is a public class that provides utility methods for working with file paths.

private static Object walkFull(Object object, AtomicInteger i)

private static Object walkFull(Object object, AtomicInteger i) {
    if (object instanceof Map) {
        Map map = (Map) object;
        ((Map<?, ?>) object).keySet().forEach(key -> {
            walkFull(map.get(key), i);
            i.incrementAndGet();
        });
    } else if (object instanceof List) {
        List list = (List) object;
        list.forEach(o -> {
            walkFull(o, i);
            i.incrementAndGet();
        });
    } else {
        return i.incrementAndGet();
    }
    return i;
}

The walkFull method in class io.nats.jparse.node.support.PathUtils is a recursive method that performs a full traversal of an input object and returns the total number of nodes visited.

Here is a step-by-step description of what the walkFull method does:

  1. The method takes two parameters:

    • object - the input object to be traversed
    • i - an AtomicInteger used to track the number of nodes visited
  2. The method checks if the input object is an instance of a Map:

    • If object is a Map, it casts it to a Map and proceeds to step 3.
    • If not, it checks if the input object is an instance of a List:
      • If object is a List, it casts it to a List and proceeds to step 4.
      • If not, it proceeds to step 5.
  3. When object is a Map, it iterates over all the keys in the map using the keySet().forEach method:

    • For each key, it recursively calls the walkFull method passing the value associated with that key and the i parameter.
    • After the recursive call, it increments the value of i by calling i.incrementAndGet().
  4. When object is a List, it iterates over all the elements in the list using the forEach method:

    • For each element, it recursively calls the walkFull method passing the element and the i parameter.
    • After the recursive call, it increments the value of i by calling i.incrementAndGet().
  5. When object is neither a Map nor a List, it means it is a leaf node in the traversal and cannot be further traversed.

    • In this case, it returns the current value of i incremented by calling i.incrementAndGet().
  6. After traversing all the nodes in the input object, the method returns the final value of i.

The purpose of this method is to recursively traverse a given object and count the number of nodes (maps, lists, and leaf nodes) present in the object.

sequence diagram

MockTokenSubList

MockTokenSubList Class

The MockTokenSubList class is a public class that extends the TokenSubList class. This class serves as a mock implementation of the TokenSubList class. It provides a subset view of a list of tokens, allowing for efficient processing and manipulation of the token data. By extending the TokenSubList class, the MockTokenSubList class inherits the behavior and functionality of the parent class, while also providing additional mock functionality for testing purposes.

CharArrayUtils

The CharArrayUtils class is a utility class designed for working with character arrays. It provides various methods and functions that can be used to manipulate and perform operations on character arrays efficiently.

public static String decodeJsonString(char[] chars, int startIndex, int endIndex)

public static String decodeJsonString(char[] chars, int startIndex, int endIndex) {
    int length = endIndex - startIndex;
    char[] builder = new char[calculateLengthAfterEncoding(chars, startIndex, endIndex, length)];
    char c;
    int index = startIndex;
    int idx = 0;
    while (true) {
        c = chars[index];
        if (c == '\\' && index < (endIndex - 1)) {
            index++;
            c = chars[index];
            if (c != 'u') {
                builder[idx] = controlMap[c];
                idx++;
            } else {
                if (index + 4 < endIndex) {
                    char unicode = getUnicode(chars, index);
                    builder[idx] = unicode;
                    index += 4;
                    idx++;
                }
            }
        } else {
            builder[idx] = c;
            idx++;
        }
        if (index >= (endIndex - 1)) {
            break;
        }
        index++;
    }
    return new String(builder);
}

The decodeJsonString method, defined in the io.nats.jparse.node.support.CharArrayUtils class, decodes a JSON string from a character array. Here's a step-by-step description of what the method does:

  1. The method takes three parameters: chars is the character array containing the JSON string, startIndex is the starting index of the substring to decode, and endIndex is the ending index of the substring to decode.

  2. It calculates the length of the substring by subtracting the startIndex from the endIndex.

  3. It creates a new character array called builder with a length determined by the calculateLengthAfterEncoding method, passing in the chars, startIndex, endIndex, and length parameters.

  4. It initializes a variable c to store the current character being processed, an index variable to keep track of the current index in the chars array, and an idx variable to keep track of the current index in the builder array.

  5. Enter a loop that continues until break is called:

    1. Get the current character c from the chars array at the current index.
    2. Check if the current character c is a single quote (') and if the index is less than (<) (endIndex - 1).
    3. If the above conditions are satisfied, increment the index by 1 and get the next character c from the chars array.
      • If the next character c is not a lowercase 'u', it means it is not a Unicode character, so it can be converted to its corresponding control character using the controlMap array. The converted character is then stored in the builder array at the current idx index, and idx is incremented by 1.
      • If the next character c is a lowercase 'u', it means it is a Unicode character. Check if there are at least 4 more characters (index + 4 < endIndex) in the chars array to form a complete Unicode escape sequence. If so, call the getUnicode method to convert the escape sequence to the corresponding Unicode character, store it in the builder array at the current idx index, and increment both index and idx by 1.
    4. If the current character c is not a single quote, store it in the builder array at the current idx index, and increment idx by 1.
    5. Check if the index is greater than or equal to (endIndex - 1). If so, break the loop.
  6. Convert the builder character array to a string using the String constructor and return the decoded JSON string.

Note: The specific details of the calculateLengthAfterEncoding and getUnicode methods are not provided in the given code snippet, so their functionality cannot be described accurately.

sequence diagram

private static int calculateLengthAfterEncoding(char[] chars, int startIndex, int endIndex, int length)

private static int calculateLengthAfterEncoding(char[] chars, int startIndex, int endIndex, int length) {
    char c;
    int index = startIndex;
    int controlCharCount = length;
    while (true) {
        c = chars[index];
        if (c == '\\' && index < (endIndex - 1)) {
            index++;
            c = chars[index];
            if (c != 'u') {
                controlCharCount -= 1;
            } else {
                if (index + 4 < endIndex) {
                    controlCharCount -= 5;
                    index += 4;
                }
            }
        }
        if (index >= (endIndex - 1)) {
            break;
        }
        index++;
    }
    return controlCharCount;
}

The calculateLengthAfterEncoding method in the CharArrayUtils class is used to calculate the length of a character array after encoding.

Here is a step-by-step description of what this method does:

  1. Start by initializing the variables c, index, and controlCharCount with the given startIndex, length, and startIndex, respectively.

  2. Enter an infinite while loop.

  3. At each iteration of the loop, get the character c at the current index index from the chars array.

  4. Check if the character c is a single quote (') and if the current index index is less than the endIndex - 1. If both conditions are true, proceed further.

  5. Increment the index by 1 and get the next character c at the updated index.

  6. Check if the character c is not equal to the letter 'u'. If true, decrement the controlCharCount by 1.

  7. If the character c is equal to 'u', check if the current index index plus 4 is less than the endIndex. If true, proceed further.

  8. Decrement the controlCharCount by 5.

  9. Increment the index by 4 to skip over the following 4 characters.

  10. Check if the index is greater than or equal to endIndex - 1. If true, break out of the loop and exit.

  11. Increment the index by 1.

  12. Go back to the start of the loop.

  13. After exiting the loop, return the final value of controlCharCount.

Note: This method is used to calculate the length of the character array after encoding by subtracting the length of certain special characters.

sequence diagram

public static boolean hasEscapeChar(char[] array, int startIndex, int endIndex)

public static boolean hasEscapeChar(char[] array, int startIndex, int endIndex) {
    char currentChar;
    for (int index = startIndex; index < endIndex; index++) {
        currentChar = array[index];
        if (currentChar == ESCAPE) {
            return true;
        }
    }
    return false;
}

Method Name: hasEscapeChar

Description:

This method, hasEscapeChar, is defined in the io.nats.jparse.node.support.CharArrayUtils class. It takes in a character array array, a start index startIndex, and an end index endIndex as parameters.

The method iterates through the array from the startIndex to the endIndex (exclusive) using a for loop. For each character currentChar in the array, it checks if it is equal to a constant called ESCAPE.

If the currentChar is equal to ESCAPE, the method returns true. Otherwise, it continues to the next character in the array.

If no character in the range from startIndex to endIndex is equal to ESCAPE, the method returns false.

sequence diagram

TokenSubList

A TokenSubList is a Java class that extends the AbstractList<Token> interface. It represents a sublist implementation for storing a portion of tokens from a TokenList. This class provides methods for accessing tokens within the sublist, getting the size of the sublist, creating sublists, converting the sublist to an array, and counting the number of children tokens within a specified range relative to a root token. It is designed to be used as part of tokenization or parsing processes in software engineering.

public int countChildren(final int from, final Token rootToken)

public int countChildren(final int from, final Token rootToken) {
    int idx = from;
    int count = 0;
    final Token[] tokens = this.tokens;
    final int length = this.size;
    final int offset = this.offset;
    final int rootTokenStart = rootToken.startIndex;
    final int rootTokenEnd = rootToken.endIndex;
    for (; idx < length; idx++) {
        Token token = tokens[idx + offset];
        if (token.startIndex >= rootTokenStart && token.endIndex <= rootTokenEnd) {
            count++;
        } else {
            break;
        }
    }
    return count;
}

The countChildren method in the TokenSubList class, defined in the io.nats.jparse.node.support package, counts the number of child tokens within a specified range, starting from a given index.

Here are the step-by-step details of what the method does based on its body:

  1. Declare two integer variables, idx and count, and initialize them to the values of the from parameter, which represents the starting index, and 0 respectively.
  2. Create a reference to the tokens array of the current TokenSubList instance.
  3. Assign the size of the TokenSubList object to the length variable for easier access.
  4. Assign the offset of the TokenSubList object to the offset variable for easier access.
  5. Get the startIndex and endIndex of the provided rootToken object.
  6. Start a loop which iterates from idx to length - 1.
  7. Inside the loop: a. Get the Token object at the current idx + offset index from the tokens array. b. Check if the startIndex of the token is greater than or equal to the rootTokenStart and the endIndex is less than or equal to the rootTokenEnd. c. If the above condition is true, increment the count variable by one. d. If the above condition is false, break out of the loop.
  8. Return the final value of the count variable, which represents the number of child tokens within the specified range and starting from the given index.

sequence diagram

TokenList

The TokenList class is an implementation of a list that stores tokens. It provides methods for adding tokens, accessing tokens by index, clearing the list, creating sub lists, and more. The class also includes methods for managing placeholder tokens and creating compact clones of the list.

@Override

public final boolean add(Token token)

@Override
public final boolean add(Token token) {
    final int length = tokens.length;
    if (index >= length) {
        final Token[] newTokens = new Token[length * 2];
        System.arraycopy(tokens, 0, newTokens, 0, length);
        tokens = newTokens;
    }
    tokens[index] = token;
    index++;
    return true;
}

The add method in class io.nats.jparse.node.support.TokenList is used to add a Token object to the list of tokens.

Here is a step-by-step description of how the method works:

  1. The method is marked as @Override, indicating that it is overriding a method from a superclass or interface.

  2. The method signature indicates that it returns a boolean value.

  3. The method takes a single parameter, token, which is of type Token. This is the object that will be added to the list.

  4. The method begins by getting the length of the current tokens array.

  5. It checks if the index variable (which presumably tracks the current index where the next token will be added) is greater than or equal to the length of the array. If it is, this means that the array is full and needs to be expanded.

  6. If the array needs to be expanded, a new array, newTokens, is created with a length that is double the current length of the array.

  7. The System.arraycopy method is then used to copy the contents of the tokens array into the newTokens array. This ensures that the existing tokens are retained in the new array.

  8. The tokens member variable is then updated to point to the new array.

  9. The token parameter is added to the tokens array at the current index position.

  10. The index variable is incremented to prepare for the next token to be added.

  11. Finally, the method returns true, indicating that the token was successfully added to the list.

Overall, the add method ensures that the list of tokens has sufficient capacity to accommodate new tokens, and adds the specified token to the list, updating the index accordingly.

sequence diagram

public void placeHolder()

public void placeHolder() {
    final int length = tokens.length;
    if (index >= length) {
        final Token[] newTokens = new Token[length * 2];
        System.arraycopy(tokens, 0, newTokens, 0, length);
        tokens = newTokens;
    }
    index++;
}

The placeHolder method in the io.nats.jparse.node.support.TokenList class is used to increment the index of the TokenList object and potentially expand its internal array if the index is greater than or equal to the length of the array.

Here is a step-by-step description of what the placeHolder method does based on its body:

  1. Get the length of the tokens array using the length field.
  2. Check if the current value of the index field is greater than or equal to the length of the tokens array.
  3. If the condition is true, it means that the TokenList object has reached the end of its internal array.
  4. In this case, create a new array of Token objects named newTokens with a length of twice the current length of the tokens array.
  5. Use the System.arraycopy method to copy the elements from the existing tokens array to the newTokens array.
    • The starting index for copying is 0 (meaning copying from the beginning of the array).
    • The destination array is the newTokens array.
    • The starting index for pasting in the destination array is also 0.
    • The number of elements to copy is equal to the length of the tokens array.
  6. Set the tokens field of the TokenList object to the newTokens array. This effectively expands the internal array.
  7. Increment the value of the index field by 1.

The purpose of this method is to provide a mechanism for adding new Token objects to the TokenList object. If the index is already at the end of the array, the method will automatically expand the array to accommodate the new elements.

sequence diagram

public TokenList compactClone()

public TokenList compactClone() {
    final int length = index;
    final Token[] newTokens = new Token[index];
    System.arraycopy(tokens, 0, newTokens, 0, length);
    return new TokenList(newTokens);
}

The compactClone method in the TokenList class is used to create a new instance of TokenList that contains a compact clone of the tokens in the original TokenList object.

Here is a step-by-step description of what the method does:

  1. It starts by creating a local variable length and assigning it the value of the index field of the current object. This index field represents the number of tokens currently stored in the TokenList object.

  2. It then creates a new array of Token objects called newTokens with a length equal to the index field. This ensures that the new array is large enough to hold all the tokens.

  3. It uses the System.arraycopy method to copy the contents of the tokens array from the original TokenList object to the newTokens array. This ensures that the newTokens array contains a clone of the tokens in the original array.

  4. Finally, it creates a new TokenList object by passing the newTokens array to its constructor and returns this new object.

In summary, the compactClone method creates a new TokenList object that contains a compact clone of the tokens in the original TokenList object by copying the contents of the tokens array to a new array.

sequence diagram

CharSequenceUtils

The CharSequenceUtils class is a utility class that provides various methods for working with CharSequence objects. It offers convenient functions to manipulate and process character sequences efficiently.

NodeUtils

The NodeUtils class is a utility class that provides functionality for working with Node objects. It provides various methods to perform operations on nodes, making it easier to manipulate and interact with these objects.

public static Node createNode(final List tokens, final CharSource source, boolean objectsKeysCanBeEncoded)

public static Node createNode(final List<Token> tokens, final CharSource source, boolean objectsKeysCanBeEncoded) {
    final NodeType nodeType = NodeType.tokenTypeToElement(tokens.get(0).type);
    switch(nodeType) {
        case ARRAY:
            return new ArrayNode((TokenSubList) tokens, source, objectsKeysCanBeEncoded);
        case INT:
            return new NumberNode(tokens.get(0), source, NodeType.INT);
        case FLOAT:
            return new NumberNode(tokens.get(0), source, NodeType.FLOAT);
        case OBJECT:
            return new ObjectNode((TokenSubList) tokens, source, objectsKeysCanBeEncoded);
        case STRING:
            return new StringNode(tokens.get(0), source);
        case BOOLEAN:
            return new BooleanNode(tokens.get(0), source);
        case NULL:
            return new NullNode(tokens.get(0), source);
        case PATH_INDEX:
            return new IndexPathNode(tokens.get(0), source);
        case PATH_KEY:
            return new KeyPathNode(tokens.get(0), source);
        default:
            throw new IllegalStateException();
    }
}

The createNode method in the NodeUtils class is responsible for creating a Node object based on the provided list of tokens and other parameters. Here is a step-by-step explanation of what this method does:

  1. The method takes three parameters:

    • tokens - a list of Token objects
    • source - a CharSource object
    • objectsKeysCanBeEncoded - a boolean value indicating whether object keys can be encoded
  2. It extracts the type of the first token in the list using the type field of the Token object and maps it to a corresponding NodeType using NodeType.tokenTypeToElement(tokens.get(0).type).

  3. It uses a switch statement to determine the type of Node object to create based on the mapped NodeType.

  4. If the NodeType is ARRAY, it creates a new ArrayNode object using the tokens, source, and objectsKeysCanBeEncoded parameters, and returns it.

  5. If the NodeType is INT, it creates a new NumberNode object with the first token, source, and NodeType.INT, and returns it.

  6. If the NodeType is FLOAT, it creates a new NumberNode object with the first token, source, and NodeType.FLOAT, and returns it.

  7. If the NodeType is OBJECT, it creates a new ObjectNode object using the tokens, source, and objectsKeysCanBeEncoded parameters, and returns it.

  8. If the NodeType is STRING, it creates a new StringNode object with the first token and source, and returns it.

  9. If the NodeType is BOOLEAN, it creates a new BooleanNode object with the first token and source, and returns it.

  10. If the NodeType is NULL, it creates a new NullNode object with the first token and source, and returns it.

  11. If the NodeType is PATH_INDEX, it creates a new IndexPathNode object with the first token and source, and returns it.

  12. If the NodeType is PATH_KEY, it creates a new KeyPathNode object with the first token and source, and returns it.

  13. If none of the above NodeType cases match, it throws an IllegalStateException.

That's the step-by-step description of the createNode method in the NodeUtils class.

sequence diagram

public static Node createNodeForObject(final List theTokens, final CharSource source, boolean objectsKeysCanBeEncoded)

public static Node createNodeForObject(final List<Token> theTokens, final CharSource source, boolean objectsKeysCanBeEncoded) {
    final Token rootToken = theTokens.get(1);
    final List<Token> tokens = theTokens.subList(1, theTokens.size());
    final NodeType nodeType = NodeType.tokenTypeToElement(rootToken.type);
    switch(nodeType) {
        case ARRAY:
            return new ArrayNode((TokenSubList) tokens, source, objectsKeysCanBeEncoded);
        case INT:
            return new NumberNode(tokens.get(0), source, NodeType.INT);
        case FLOAT:
            return new NumberNode(tokens.get(0), source, NodeType.FLOAT);
        case OBJECT:
            return new ObjectNode((TokenSubList) tokens, source, objectsKeysCanBeEncoded);
        case STRING:
            return new StringNode(tokens.get(0), source);
        case BOOLEAN:
            return new BooleanNode(tokens.get(0), source);
        case NULL:
            return new NullNode(tokens.get(0), source);
        default:
            throw new IllegalStateException();
    }
}

The createNodeForObject method, defined in the NodeUtils class in the io.nats.jparse.node.support package, takes in a list of tokens, a character source, and a boolean indicating whether object keys can be encoded. It returns a Node object based on the type of the root token.

Here is a step-by-step description of what the method does:

  1. It retrieves the root token from the input list of tokens and assigns it to the local variable rootToken.
  2. It creates a new sublist of tokens (tokens) by excluding the first token (root token) from the input list using the subList method.
  3. It determines the NodeType (enumeration) corresponding to the type of the root token using the tokenTypeToElement method and assigns it to the local variable nodeType.
  4. It uses a switch statement on the nodeType to handle different cases:
    • If the nodeType is ARRAY, it creates a new ArrayNode object passing in the tokens, source, and objectsKeysCanBeEncoded arguments and returns it.
    • If the nodeType is INT, it creates a new NumberNode object passing in the first token from tokens, source, and the NodeType.INT enum value, and returns it.
    • If the nodeType is FLOAT, it creates a new NumberNode object passing in the first token from tokens, source, and the NodeType.FLOAT enum value, and returns it.
    • If the nodeType is OBJECT, it creates a new ObjectNode object passing in the tokens, source, and objectsKeysCanBeEncoded arguments and returns it.
    • If the nodeType is STRING, it creates a new StringNode object passing in the first token from tokens and source, and returns it.
    • If the nodeType is BOOLEAN, it creates a new BooleanNode object passing in the first token from tokens and source, and returns it.
    • If the nodeType is NULL, it creates a new NullNode object passing in the first token from tokens and source, and returns it.
    • If none of the above cases match, it throws an IllegalStateException.
  5. The method ends.

This method is responsible for creating and returning different types of Node objects based on the type of the root token.

sequence diagram

NumberParseResult

NumberParseResult

The NumberParseResult class represents the result of a number parsing operation. It provides methods to access the end index of the parsed number and to check if the parsed number was a float. The class also overrides the equals, hashCode, and toString methods for proper object comparison and string representation.

io.nats.jparse.parser.indexoverlay

class diagram

JsonStrictParser

The JsonStrictParser class is an implementation of the JsonParser interface. It uses a strict JSON parsing algorithm and does not accept JSON strings that are not strictly compliant with the JSON RFC.

private List scan(final CharSource source, TokenList tokens)

private List<Token> scan(final CharSource source, TokenList tokens) {
    nestLevel = 0;
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, tokens);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, tokens);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, tokens);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, tokens);
            break;
        case NULL_START:
            parseNull(source, tokens);
            break;
        case STRING_START_TOKEN:
            parseString(source, tokens);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, tokens);
            break;
        default:
            throw new UnexpectedCharacterException("Scanning JSON", "Unexpected character", source, (char) ch);
    }
    source.checkForJunk();
    return tokens;
}

The scan method is defined in the JsonStrictParser class in the package io.nats.jparse.parser.indexoverlay. It takes two parameters: a CharSource object and a TokenList object.

Here is a step-by-step description of what the scan method does based on its body:

  1. It initializes the nestLevel variable to 0.

  2. It calls the nextSkipWhiteSpace method on the source object to get the next non-whitespace character and assigns it to the ch variable.

  3. It uses a switch statement to perform different actions based on the value of ch:

    • If ch is equal to OBJECT_START_TOKEN, it calls the parseObject method, passing the source and tokens objects.

    • If ch is equal to ARRAY_START_TOKEN, it calls the parseArray method, passing the source and tokens objects.

    • If ch is equal to TRUE_BOOLEAN_START, it calls the parseTrue method, passing the source and tokens objects.

    • If ch is equal to FALSE_BOOLEAN_START, it calls the parseFalse method, passing the source and tokens objects.

    • If ch is equal to NULL_START, it calls the parseNull method, passing the source and tokens objects.

    • If ch is equal to STRING_START_TOKEN, it calls the parseString method, passing the source and tokens objects.

    • If ch is equal to any of the number-related tokens (NUM_0, NUM_1, ..., NUM_9, MINUS, PLUS), it calls the parseNumber method, passing the source and tokens objects.

    • If none of the above cases match, it throws an UnexpectedCharacterException with a message indicating that an unexpected character was encountered.

  4. After the switch statement, it calls the checkForJunk method on the source object to check if there is any remaining junk after parsing the JSON.

  5. Finally, it returns the tokens object.

That's the step-by-step description of what the scan method does based on its body. sequence diagram

private void parseArray(final CharSource source, final TokenList tokens)

private void parseArray(final CharSource source, final TokenList tokens) {
    levelCheck(source);
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseArrayItem(source, tokens);
        if (!done) {
            done = source.findCommaOrEndForArray();
        }
    }
    final Token arrayToken = new Token(startSourceIndex, source.getIndex(), TokenTypes.ARRAY_TOKEN);
    tokens.set(tokenListIndex, arrayToken);
}

The parseArray method in class io.nats.jparse.parser.indexoverlay.JsonStrictParser is responsible for parsing an array from a given character source.

Here is a step-by-step description of what this method does:

  1. It starts by calling the levelCheck method, which verifies that the parser is at the correct level before parsing the array.
  2. It stores the current index of the character source and the index of the token list.
  3. It adds a placeholder token to the token list using the tokens.placeHolder() method.
  4. It enters a loop which continues until the parsing of the array is done.
  5. Inside the loop, it calls the parseArrayItem method to parse each item in the array. The method returns a boolean indicating whether the parsing is done or not.
  6. If the parsing is not done, it calls the source.findCommaOrEndForArray() method to find the next comma or the end of the array in the character source.
  7. The loop continues until the parsing of the array is done.
  8. After the loop, it creates a Token object representing the parsed array, using the start index and the current index of the character source. The token type is set to TokenTypes.ARRAY_TOKEN.
  9. Finally, it updates the placeholder token in the token list with the newly created array token using the tokens.set(tokenListIndex, arrayToken) method.

In summary, the parseArray method iterates over the items in the array, parses each item using the parseArrayItem method, and then creates a token representing the parsed array. sequence diagram

private boolean parseArrayItem(CharSource source, TokenList tokens)

private boolean parseArrayItem(CharSource source, TokenList tokens) {
    char startChar = source.getCurrentChar();
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, tokens);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, tokens);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, tokens);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, tokens);
            break;
        case NULL_START:
            parseNull(source, tokens);
            break;
        case STRING_START_TOKEN:
            parseString(source, tokens);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, tokens);
            if (source.getCurrentChar() == ARRAY_END_TOKEN || source.getCurrentChar() == ARRAY_SEP) {
                if (source.getCurrentChar() == ARRAY_END_TOKEN) {
                    source.next();
                    return true;
                }
            }
            break;
        case ARRAY_END_TOKEN:
            if (startChar == ARRAY_SEP) {
                throw new UnexpectedCharacterException("Parsing Array Item", "Trailing comma", source, (char) ch);
            }
            source.next();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing Array Item", "Unexpected character", source, (char) ch);
    }
    return false;
}

The parseArrayItem method is defined in the JsonStrictParser class in the io.nats.jparse.parser.indexoverlay package. It takes two parameters, CharSource source and TokenList tokens, and returns a boolean value.

Here is a step-by-step description of what the parseArrayItem method does based on its body:

  1. It retrieves the current character from the source using the getCurrentChar method and assigns it to the startChar variable.
  2. It reads the next character from the source while skipping any whitespaces using the nextSkipWhiteSpace method and assigns it to the ch variable.
  3. It performs a switch-case statement based on the value of ch to determine the action to take:
    • If ch is equal to OBJECT_START_TOKEN, it calls the parseObject method passing source and tokens as arguments.
    • If ch is equal to ARRAY_START_TOKEN, it calls the parseArray method passing source and tokens as arguments.
    • If ch is equal to TRUE_BOOLEAN_START, it calls the parseTrue method passing source and tokens as arguments.
    • If ch is equal to FALSE_BOOLEAN_START, it calls the parseFalse method passing source and tokens as arguments.
    • If ch is equal to NULL_START, it calls the parseNull method passing source and tokens as arguments.
    • If ch is equal to STRING_START_TOKEN, it calls the parseString method passing source and tokens as arguments.
    • If ch is equal to any of the numeric characters (0-9) or the symbols '-' or '+', it calls the parseNumber method passing source and tokens as arguments.
      • After parsing the number, if the current character is either ARRAY_END_TOKEN or ARRAY_SEP, it checks if the current character is ARRAY_END_TOKEN.
        • If it is, it advances the source using the next method and returns true.
    • If ch is equal to ARRAY_END_TOKEN, it first checks if the startChar is ARRAY_SEP.
      • If it is, it throws an UnexpectedCharacterException with the message "Parsing Array Item: Trailing comma", source and the character as arguments.
      • If it is not, it advances the source using the next method and returns true.
    • If none of the above cases match, it throws an UnexpectedCharacterException with the message "Parsing Array Item: Unexpected character", source, and the character as arguments.
  4. If none of the cases match, it returns false.

The purpose of the parseArrayItem method is to parse an item within a JSON array and add tokens to the TokenList for further processing.

private boolean parseKey(final CharSource source, final TokenList tokens)

private boolean parseKey(final CharSource source, final TokenList tokens) {
    final char startChar = source.getCurrentChar();
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex() - 1;
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean found = false;
    switch(ch) {
        case STRING_START_TOKEN:
            final int strStartIndex = startIndex + 1;
            final int strEndIndex;
            if (objectsKeysCanBeEncoded) {
                strEndIndex = source.findEndOfEncodedString();
            } else {
                strEndIndex = source.findEndString();
            }
            tokens.add(new Token(strStartIndex + 1, strEndIndex, TokenTypes.STRING_TOKEN));
            found = true;
            break;
        case OBJECT_END_TOKEN:
            if (startChar == OBJECT_ATTRIBUTE_SEP) {
                throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
            }
            tokens.undoPlaceholder();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        tokens.set(tokenListIndex, new Token(startIndex + 1, source.getIndex(), TokenTypes.ATTRIBUTE_KEY_TOKEN));
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

The parseKey method in the JsonStrictParser class is responsible for parsing a key from a CharSource and adding it to a TokenList. Here is a step-by-step description of this method:

  1. The method starts by getting the current character from the CharSource.
  2. It then skips any whitespace characters and gets the next character from the CharSource.
  3. The current index of the CharSource is stored as the starting index of the key.
  4. The current index of the TokenList is stored as the token list index.
  5. A placeholder token is added to the TokenList.
  6. A switch statement is used to handle different cases based on the value of the next character:
    • If the next character is a STRING_START_TOKEN, the method proceeds to extract the string key.
    • If the next character is an OBJECT_END_TOKEN, the method checks if the start character is an OBJECT_ATTRIBUTE_SEP and throws an exception if so. Otherwise, it undoes the placeholder token and returns true.
    • If none of the above cases match, an exception is thrown.
  7. If a string key is found, the method determines the end index of the string and adds a new token to the TokenList representing the string key.
  8. A boolean variable done is set based on whether the method can find the end of the object or the attribute separator.
  9. If the parsing is not done (!done) and a key is found (found is true), the method updates the placeholder token in the TokenList with the start and end indices of the key.
  10. If a key is found (found is true) and the parsing is done (done is true), an exception is thrown.
  11. Finally, the method returns the value of done.

This method is responsible for parsing a key from a JSON object and updating the TokenList with the parsed key token. sequence diagram

private boolean parseValue(final CharSource source, TokenList tokens)

private boolean parseValue(final CharSource source, TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, tokens);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, tokens);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, tokens);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, tokens);
            break;
        case NULL_START:
            parseNull(source, tokens);
            break;
        case STRING_START_TOKEN:
            parseString(source, tokens);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, tokens);
            break;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, ch);
    }
    source.skipWhiteSpace();
    switch(source.getCurrentChar()) {
        case OBJECT_END_TOKEN:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return true;
        case OBJECT_ATTRIBUTE_SEP:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return false;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, source.getCurrentChar());
    }
}

The parseValue method in the JsonStrictParser class is responsible for parsing a single JSON value from a given character source (source) and adding the corresponding token to a token list (tokens). Here is a step-by-step description of what the method does:

  1. Read the next character from the character source while skipping any white space characters.
  2. Get the current index of the character source (startIndex) and the current index of the token list (tokenListIndex).
  3. Add a placeholder token to the token list at the current index.
  4. Use a switch statement to check the value of the character read in step 1.
    • If the character is the start of an object (OBJECT_START_TOKEN), call the parseObject method to parse the object and add the corresponding token(s) to the token list.
    • If the character is the start of an array (ARRAY_START_TOKEN), call the parseArray method to parse the array and add the corresponding token(s) to the token list.
    • If the character is the start of the boolean value true (TRUE_BOOLEAN_START), call the parseTrue method to parse the boolean value and add the corresponding token(s) to the token list.
    • If the character is the start of the boolean value false (FALSE_BOOLEAN_START), call the parseFalse method to parse the boolean value and add the corresponding token(s) to the token list.
    • If the character is the start of the value null (NULL_START), call the parseNull method to parse the null value and add the corresponding token(s) to the token list.
    • If the character is the start of a string (STRING_START_TOKEN), call the parseString method to parse the string value and add the corresponding token(s) to the token list.
    • If the character is a digit (NUM_0 to NUM_9) or a sign (MINUS or PLUS), call the parseNumber method to parse the number value and add the corresponding token(s) to the token list.
    • If the character does not match any of the above cases, throw an UnexpectedCharacterException with an appropriate error message.
  5. Skip any white space characters after parsing the value.
  6. Use a switch statement to check the current character in the character source.
    • If the character is the end of an object (OBJECT_END_TOKEN), check if the current index of the character source is the same as the tokenListIndex. If they are equal, throw an UnexpectedCharacterException with an error message indicating "Key separator before value". Otherwise, set the token at the tokenListIndex to be a new token representing the parsed value and return true from the method.
    • If the character is the attribute separator (OBJECT_ATTRIBUTE_SEP), check if the current index of the character source is the same as the tokenListIndex. If they are equal, throw an UnexpectedCharacterException with an error message indicating "Key separator before value". Otherwise, set the token at the tokenListIndex to be a new token representing the parsed value and return false from the method.
    • If the current character does not match any of the above cases, throw an UnexpectedCharacterException with an appropriate error message. sequence diagram

private void parseObject(final CharSource source, TokenList tokens)

private void parseObject(final CharSource source, TokenList tokens) {
    levelCheck(source);
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseKey(source, tokens);
        if (!done)
            done = parseValue(source, tokens);
    }
    source.next();
    tokens.set(tokenListIndex, new Token(startSourceIndex, source.getIndex(), TokenTypes.OBJECT_TOKEN));
}

The parseObject method is a private method defined in the class io.nats.jparse.parser.indexoverlay.JsonStrictParser. This method takes two parameters: source, which is an instance of the CharSource class, and tokens, which is an instance of the TokenList class.

The purpose of this method is to parse an object from the given source and add the corresponding token to the tokens list.

Here is a step-by-step description of what the parseObject method is doing based on its body:

  1. The method starts by calling the levelCheck method passing the source as a parameter, which ensures that the nesting level of the JSON object is within certain bounds.

  2. The next two lines of code store the current index of the source and the index of the tokens list.

  3. The method then adds a placeholder token to the tokens list using the placeHolder method.

  4. The method enters a loop that continues until the parsing is done.

  5. Inside the loop, the method first calls the parseKey method passing the source and tokens as parameters. The parseKey method is not shown in the provided code, but it is assumed to return a boolean value indicating whether the key parsing is done or not. The return value is stored in the done variable.

  6. If the key parsing is not done (i.e., done is false), the method calls the parseValue method passing the source and tokens as parameters. The parseValue method is not shown in the provided code, but it is assumed to return a boolean value indicating whether the value parsing is done or not. The return value is also stored in the done variable.

  7. The loop continues until both the key and value parsing is done.

  8. After the loop is done, the next line of code calls the next method on the source to advance to the next character in the input.

  9. Finally, the last line of code sets the token at the tokenListIndex in the tokens list to a new Token object. This Token object is created with the startSourceIndex, which is the starting index of the source, the current index of the source, and the TokenTypes.OBJECT_TOKEN type.

Overall, the parseObject method is responsible for parsing a JSON object from the source and adding a token representing that object to the tokens list. sequence diagram

private void levelCheck(CharSource source)

private void levelCheck(CharSource source) {
    nestLevel++;
    if (nestLevel > NEST_LEVEL) {
        throw new UnexpectedCharacterException("Next level violation", "Too many levels " + nestLevel, source);
    }
}

Method: levelCheck

This method is defined in the JsonStrictParser class, which resides in the io.nats.jparse.parser.indexoverlay package. It performs the following steps:

  1. Increments the value of the nestLevel variable by 1.
  2. Checks if the value of nestLevel is greater than NEST_LEVEL.
  3. If the condition evaluates to true, it throws an UnexpectedCharacterException.
    • The exception message is set to "Next level violation".
    • The exception details include "Too many levels" followed by the value of nestLevel.
    • The source parameter is also passed to the exception constructor.

This method is used to ensure that the nesting level in a JSON input does not exceed a predefined threshold (NEST_LEVEL). If the nesting level exceeds this threshold, an exception is thrown. sequence diagram

JsonFastParser

The JsonFastParser class is an implementation of the JsonParser interface that provides methods for scanning and parsing JSON strings. It offers functionality for scanning a character source and returning a list of tokens, as well as parsing a character source and returning a root node representing the parsed JSON. The class also includes default methods for parsing and scanning strings and extends the ParseConstants interface, which defines constants used for parsing JSON strings.

private List scan(final CharSource source, TokenList tokens)

private List<Token> scan(final CharSource source, TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, tokens);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, tokens);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, tokens);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, tokens);
            break;
        case NULL_START:
            parseNull(source, tokens);
            break;
        case STRING_START_TOKEN:
            parseString(source, tokens);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, tokens);
            break;
        default:
            throw new UnexpectedCharacterException("Scanning JSON", "Unexpected character", source, (char) ch);
    }
    return tokens;
}

The method scan is defined in the JsonFastParser class in the package io.nats.jparse.parser.indexoverlay. It takes two parameters, a CharSource object named source and a TokenList object named tokens. The purpose of this method is to scan the given input source character by character and determine the appropriate action to take based on the encountered character.

Here is a step-by-step description of what the scan method does based on its body:

  1. The method starts by calling the nextSkipWhiteSpace method on the source object, which returns the next non-whitespace character from the input source. This character is stored in the variable ch.

  2. A switch statement is used to determine the action to take based on the value of ch.

  3. If ch is equal to OBJECT_START_TOKEN, the parseObject method is called with the source and tokens objects as parameters. This method parses an object from the input source and adds the corresponding tokens to the tokens list.

  4. If ch is equal to ARRAY_START_TOKEN, the parseArray method is called with the source and tokens objects as parameters. This method parses an array from the input source and adds the corresponding tokens to the tokens list.

  5. If ch is equal to TRUE_BOOLEAN_START, the parseTrue method is called with the source and tokens objects as parameters. This method parses a true boolean value from the input source and adds the corresponding token to the tokens list.

  6. If ch is equal to FALSE_BOOLEAN_START, the parseFalse method is called with the source and tokens objects as parameters. This method parses a false boolean value from the input source and adds the corresponding token to the tokens list.

  7. If ch is equal to NULL_START, the parseNull method is called with the source and tokens objects as parameters. This method parses a null value from the input source and adds the corresponding token to the tokens list.

  8. If ch is equal to STRING_START_TOKEN, the parseString method is called with the source and tokens objects as parameters. This method parses a string from the input source and adds the corresponding token to the tokens list.

  9. If ch is equal to NUM_0, NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, NUM_9, MINUS, or PLUS, the parseNumber method is called with the source and tokens objects as parameters. This method parses a number from the input source and adds the corresponding token to the tokens list.

  10. If none of the above cases match, an UnexpectedCharacterException is thrown with an error message indicating that the character is unexpected.

  11. Finally, the tokens list is returned.

In summary, the scan method scans the input source character by character and performs different parsing actions based on the encountered character, such as parsing objects, arrays, booleans, strings, numbers, or throwing an exception for unexpected characters. The method then returns the list of tokens that were parsed.

sequence diagram

private void parseArray(final CharSource source, final TokenList tokens)

private void parseArray(final CharSource source, final TokenList tokens) {
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseArrayItem(source, tokens);
    }
    final Token arrayToken = new Token(startSourceIndex, source.getIndex(), TokenTypes.ARRAY_TOKEN);
    tokens.set(tokenListIndex, arrayToken);
}

The parseArray method in the JsonFastParser class is responsible for parsing an array from a given CharSource and storing the resulting tokens in a TokenList object.

Here is a step-by-step description of what the method does:

  1. It starts by storing the current index of the CharSource in a variable called startSourceIndex.
  2. It also stores the current index of the TokenList in a variable called tokenListIndex.
  3. It adds a placeholder token to the TokenList using the placeHolder() method. This placeholder token will be replaced with the actual array token later.
  4. It initializes a boolean variable done to false. This variable will be used to determine when to stop parsing the array.
  5. It enters a while loop that continues until the done variable becomes true.
  6. Inside the while loop, it calls the parseArrayItem method, passing the source and tokens objects. This method is responsible for parsing a single item of the array.
  7. The return value of the parseArrayItem method is then used to update the done variable. If the parseArrayItem method returns true, it means that it has successfully parsed an array item. If it returns false, it means that there are no more array items to parse, and the loop should be terminated.
  8. After the while loop finishes, it creates a Token object called arrayToken with the start and end indices of the array in the CharSource, and the type TokenTypes.ARRAY_TOKEN.
  9. Finally, it replaces the placeholder token in the TokenList at the tokenListIndex with the arrayToken using the set method.

This completes the step-by-step description of the parseArray method in the JsonFastParser class. sequence diagram

private boolean parseArrayItem(CharSource source, TokenList tokens)

private boolean parseArrayItem(CharSource source, TokenList tokens) {
    char ch = (char) source.nextSkipWhiteSpace();
    forLoop: for (; ch != ETX; ch = (char) source.nextSkipWhiteSpace()) {
        switch(ch) {
            case OBJECT_START_TOKEN:
                parseObject(source, tokens);
                break forLoop;
            case ARRAY_START_TOKEN:
                parseArray(source, tokens);
                break forLoop;
            case TRUE_BOOLEAN_START:
                parseTrue(source, tokens);
                break forLoop;
            case FALSE_BOOLEAN_START:
                parseFalse(source, tokens);
                break forLoop;
            case NULL_START:
                parseNull(source, tokens);
                break forLoop;
            case STRING_START_TOKEN:
                parseString(source, tokens);
                break forLoop;
            case NUM_0:
            case NUM_1:
            case NUM_2:
            case NUM_3:
            case NUM_4:
            case NUM_5:
            case NUM_6:
            case NUM_7:
            case NUM_8:
            case NUM_9:
            case MINUS:
            case PLUS:
                parseNumber(source, tokens);
                break forLoop;
            case ARRAY_END_TOKEN:
                source.next();
                return true;
            case ARRAY_SEP:
                source.next();
                return false;
            default:
                throw new UnexpectedCharacterException("Parsing Array Item", "Unexpected character", source, ch);
        }
    }
    if (source.getCurrentChar() == ARRAY_END_TOKEN) {
        source.next();
        return true;
    }
    return false;
}

The method parseArrayItem in the JsonFastParser class is used to parse an individual item within a JSON array. Here is a step-by-step description of what this method does:

  1. The method takes two parameters: source, which is the source of the characters to be parsed, and tokens, which is a list to store the parsed tokens.

  2. It starts by reading the next character from the source and assigns it to the variable ch. The whitespace characters are skipped.

  3. It enters a for loop, labeled as forLoop, which iterates until the character ch is ETX (End of Text).

  4. Inside the loop, there is a switch statement that checks the value of the character ch.

  5. If ch is equal to OBJECT_START_TOKEN, it calls the parseObject method to parse the JSON object, and then break forLoop is executed, which exits the for loop.

  6. If ch is equal to ARRAY_START_TOKEN, it calls the parseArray method to parse the JSON array, and then break forLoop is executed.

  7. If ch is equal to TRUE_BOOLEAN_START, it calls the parseTrue method to parse a boolean value of true, and then break forLoop is executed.

  8. If ch is equal to FALSE_BOOLEAN_START, it calls the parseFalse method to parse a boolean value of false, and then break forLoop is executed.

  9. If ch is equal to NULL_START, it calls the parseNull method to parse a null value, and then break forLoop is executed.

  10. If ch is equal to STRING_START_TOKEN, it calls the parseString method to parse a string value, and then break forLoop is executed.

  11. If ch is a numeric digit (NUM_0 to NUM_9) or a minus or plus sign (MINUS or PLUS), it calls the parseNumber method to parse a numeric value, and then break forLoop is executed.

  12. If ch is equal to ARRAY_END_TOKEN, it moves to the next character in the source and returns true, indicating that the parsing of the array item is complete.

  13. If ch is equal to ARRAY_SEP, it moves to the next character in the source and returns false, indicating that there are more items in the array to be parsed.

  14. If none of the above cases match, it throws an UnexpectedCharacterException with an error message indicating that an unexpected character was encountered while parsing the array item.

  15. After the for loop, it checks if the current character in the source is equal to ARRAY_END_TOKEN. If it is, the method moves to the next character, returns true, indicating that the parsing of the array item is complete.

  16. If the current character is not ARRAY_END_TOKEN, the method returns false, indicating that there are additional array items to be parsed.

That's the step-by-step description of the parseArrayItem method.

private boolean parseKey(final CharSource source, final TokenList tokens)

private boolean parseKey(final CharSource source, final TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex() - 1;
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean found = false;
    switch(ch) {
        case STRING_START_TOKEN:
            final int strStartIndex = startIndex + 1;
            final int strEndIndex;
            if (objectsKeysCanBeEncoded) {
                strEndIndex = source.findEndOfEncodedString();
            } else {
                strEndIndex = source.findEndString();
            }
            tokens.add(new Token(strStartIndex + 1, strEndIndex, TokenTypes.STRING_TOKEN));
            found = true;
            break;
        case OBJECT_END_TOKEN:
            tokens.undoPlaceholder();
            return true;
        default:
            throw new UnexpectedCharacterException("Parsing key", "Unexpected character found", source);
    }
    boolean done = source.findObjectEndOrAttributeSep();
    if (!done && found) {
        tokens.set(tokenListIndex, new Token(startIndex + 1, source.getIndex(), TokenTypes.ATTRIBUTE_KEY_TOKEN));
    } else if (found && done) {
        throw new UnexpectedCharacterException("Parsing key", "Not found", source);
    }
    return done;
}

The parseKey method in the class io.nats.jparse.parser.indexoverlay.JsonFastParser is used to parse a key from a JSON object. Here is a step-by-step description of what the method does based on its body:

  1. It begins by reading the next character from the input source and skipping any whitespace characters.

  2. The method keeps track of the starting index of the key and the current index of the token list.

  3. It adds a placeholder token to the token list to reserve a position for the parsed key.

  4. The method then checks the value of the currently read character (ch) using a switch statement.

  5. If the character is the start of a string (STRING_START_TOKEN), the method proceeds to parse the string key. It determines the start position of the string key and then finds the end position by either decoding the string or finding the end of the string. It adds a new token to the token list with the start and end positions of the string key, marked as a STRING_TOKEN. It sets the "found" variable to true.

  6. If the character is an object end token (OBJECT_END_TOKEN), it means that the parsing of the current JSON object is complete. The method removes the placeholder token from the token list and returns true to indicate that the parsing of the key is done.

  7. If the character is neither a string start token nor an object end token, it throws an UnexpectedCharacterException with an appropriate error message indicating that an unexpected character was found while parsing the key.

  8. After handling the character, the method proceeds to find the end of the current JSON object or the separator between attributes.

  9. If the end of the object or attribute separator is not found and a key has been found, it updates the token list with the correct start and end positions of the key as an ATTRIBUTE_KEY_TOKEN.

  10. If a key has been found but the end of the object or attribute separator is found, it throws an UnexpectedCharacterException with an appropriate error message indicating that the end was not found.

  11. Finally, the method returns the value of the "done" variable, which indicates whether the parsing of the key is complete or not.

This method provides a step-by-step process for parsing a key from a JSON object and handles different scenarios like string keys, object ends, and unexpected characters. sequence diagram

private boolean parseValue(final CharSource source, TokenList tokens)

private boolean parseValue(final CharSource source, TokenList tokens) {
    int ch = source.nextSkipWhiteSpace();
    final int startIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    switch(ch) {
        case OBJECT_START_TOKEN:
            parseObject(source, tokens);
            break;
        case ARRAY_START_TOKEN:
            parseArray(source, tokens);
            break;
        case TRUE_BOOLEAN_START:
            parseTrue(source, tokens);
            break;
        case FALSE_BOOLEAN_START:
            parseFalse(source, tokens);
            break;
        case NULL_START:
            parseNull(source, tokens);
            break;
        case STRING_START_TOKEN:
            parseString(source, tokens);
            break;
        case NUM_0:
        case NUM_1:
        case NUM_2:
        case NUM_3:
        case NUM_4:
        case NUM_5:
        case NUM_6:
        case NUM_7:
        case NUM_8:
        case NUM_9:
        case MINUS:
        case PLUS:
            parseNumber(source, tokens);
            break;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, ch);
    }
    ch = source.skipWhiteSpace();
    switch(ch) {
        case OBJECT_END_TOKEN:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return true;
        case OBJECT_ATTRIBUTE_SEP:
            if (source.getIndex() == tokenListIndex) {
                throw new UnexpectedCharacterException("Parsing Value", "Key separator before value", source);
            }
            tokens.set(tokenListIndex, new Token(startIndex, source.getIndex(), TokenTypes.ATTRIBUTE_VALUE_TOKEN));
            return false;
        default:
            throw new UnexpectedCharacterException("Parsing Value", "Unexpected character", source, source.getCurrentChar());
    }
}

The parseValue method in the JsonFastParser class is responsible for parsing a JSON value from the provided source and populating the given tokens list with the parsed data.

Here is a step-by-step description of what the method does based on the given code:

  1. Read the next character from the source while skipping any white space characters.

  2. Get the current index of the source and the index of the tokens list.

  3. Insert a placeholder token at the current index in the tokens list.

  4. Use a switch statement to handle different types of characters:

    • If the character is { (indicating the start of an object), call the parseObject method to parse the object.
    • If the character is [ (indicating the start of an array), call the parseArray method to parse the array.
    • If the character is t (indicating the start of a true boolean value), call the parseTrue method to parse the true value.
    • If the character is f (indicating the start of a false boolean value), call the parseFalse method to parse the false value.
    • If the character is n (indicating the start of a null value), call the parseNull method to parse the null value.
    • If the character is " (indicating the start of a string), call the parseString method to parse the string.
    • If the character is a digit (indicating the start of a number) or a minus or plus sign, call the parseNumber method to parse the number.
    • If none of the above cases match, throw an UnexpectedCharacterException.
  5. Skip any remaining white space characters in the source.

  6. Use another switch statement to handle different types of characters:

    • If the character is } (indicating the end of an object), check if the current index of the source is the same as the index of the tokens list. If they are the same, throw an UnexpectedCharacterException indicating that a key separator was found before a value. Otherwise, update the token at the tokenListIndex with the start index and end index of the parsed value and return true.
    • If the character is : (indicating a key separator), check if the current index of the source is the same as the index of the tokens list. If they are the same, throw an UnexpectedCharacterException indicating that a key separator was found before a value. Otherwise, update the token at the tokenListIndex with the start index and end index of the parsed value and return false.
    • If none of the above cases match, throw an UnexpectedCharacterException indicating that an unexpected character was found.

In summary, the parseValue method parses a JSON value from the source and updates the tokens list with the start and end indexes of the parsed value. It also checks for unexpected characters or incorrect placement of key separators before values. sequence diagram

private void parseObject(final CharSource source, TokenList tokens)

private void parseObject(final CharSource source, TokenList tokens) {
    final int startSourceIndex = source.getIndex();
    final int tokenListIndex = tokens.getIndex();
    tokens.placeHolder();
    boolean done = false;
    while (!done) {
        done = parseKey(source, tokens);
        if (!done)
            done = parseValue(source, tokens);
    }
    source.next();
    tokens.set(tokenListIndex, new Token(startSourceIndex, source.getIndex(), TokenTypes.OBJECT_TOKEN));
}

The parseObject method in the JsonFastParser class is responsible for parsing a JSON object. Below is a step-by-step breakdown of what this method does based on its body:

  1. The method starts by storing the current index of the input source and the index of the token list.

  2. It creates a placeholder token in the token list to mark the starting point of the object.

  3. It initiates a loop that continues until the parsing of the object is complete. The variable done is used as a flag to indicate if the parsing is done.

  4. Within the loop, the method calls the parseKey method, passing the input source and token list. The parseKey method is expected to parse the next key in the object and return a boolean value indicating whether the parsing is done.

  5. If the parseKey method returns false, indicating that the parsing of the key is not done, the method calls the parseValue method. The parseValue method is responsible for parsing the corresponding value of the key and returns a boolean value indicating whether the parsing is done.

  6. The loop continues until either the parseKey or parseValue methods indicate that the parsing is done.

  7. After the loop, the method calls source.next() to move the input source to the next character.

  8. Finally, the method updates the token in the token list with the index range of the parsed object, using the set method. The token is marked as an "OBJECT_TOKEN" to indicate that it represents a JSON object.

Note: The specific implementation details of the parseKey and parseValue methods are not provided in the given code snippet. These methods are likely responsible for parsing the key-value pairs within the JSON object.

Clone this wiki locally