JAVA DaxTagParser

package org.daxprotocol.core.parsers;

import org.daxprotocol.core.application.DaxCoreConstants;
import org.daxprotocol.core.exceptions.DaxTagParserException;
import org.daxprotocol.core.mapper.DaxNamespaceMapper;
import org.daxprotocol.core.model.tag.DaxTag;

import java.util.ArrayList;
import java.util.List;

public class DaxTagParser {

DaxNamespaceMapper namespaceMapper;

public DaxTagParser(DaxNamespaceMapper namespaceMapper) {
this.namespaceMapper = namespaceMapper;
}

/**
* Efficiently parses an integer from a specific range within a String
* while ignoring any trailing whitespaces within that range.
*/
public int parseIntFromSequence(String seq, int start, int end) {
if (start >= end) {
throw new DaxTagParserException("Empty tag ID");
}

int num = 0;
boolean hasDigits = false;

for (int i = start; i < end; i++) {
char c = seq.charAt(i);
if (c >= '0' && c <= '9') {
num = num * 10 + (c - '0');
hasDigits = true;
} else if (c <= ' ') {
// If we found a space after digits, ensure no more digits follow
// (Standard trim-like behavior)
continue;
} else {
throw new DaxTagParserException("Invalid character: " + c);
}
}

if (!hasDigits)
throw new DaxTagParserException("No digits found");
return num;
}

/**
* Parses a string representation of a DaxTag (e.g., " CTX:100 ")
* into a DaxTag object using high-performance index tracking.
* * This version handles:
* - Leading/trailing whitespaces around the whole string.
* - Whitespaces around the separator (e.g., "CTX : 100").
* - Single-character separator defined in config.
*
* @param tagStr The raw tag string to parse.
* @return A new DaxTag object with mapped namespaceId and tagId.
* @throws DaxTagParserException if the format is invalid or tagId is not a numerical value.
*/

public DaxTag parseDaxTag(String tagStr, int msgnamespaceId) {

if (tagStr == null) {
throw new DaxTagParserException("NOT correct DaxTag: Input is null");
}

// 1. Trim the entire string without creating a new String object
int start = 0;
int end = tagStr.length();
while (start < end && tagStr.charAt(start) <= ' ') start++;
while (end > start && tagStr.charAt(end - 1) <= ' ') end--;

if (start >= end) {
throw new DaxTagParserException("NOT correct DaxTag: Input is empty or only whitespace");
}

char separator = DaxCoreConstants.NAMESPACE_TAG_SEPARATOR;
int separatorPos = -1;

// 2. Search for the separator only within the trimmed range
for (int i = start; i < end; i++) {
if (tagStr.charAt(i) == separator) {
separatorPos = i;
break;
}
}

String namespaceSymbol = null;
int tagIdStart;

if (separatorPos != -1) {
// --- namespace Prefix Found ---
// Trim the namespace symbol (handle "CTX :")
int ctxEnd = separatorPos;
while (ctxEnd > start && tagStr.charAt(ctxEnd - 1) <= ' ') {
ctxEnd--;
}

if (ctxEnd > start) {
namespaceSymbol = tagStr.substring(start, ctxEnd);
}

// Tag ID starts after the separator
tagIdStart = separatorPos + 1;
} else {
// --- No namespace Prefix ---
tagIdStart = start;
}

// 3. Trim leading spaces for Tag ID (handle ": 100")
while (tagIdStart < end && tagStr.charAt(tagIdStart) <= ' ') {
tagIdStart++;
}

// 4. Parse Tag ID directly from the sequence
int tagId;
try {
tagId = parseIntFromSequence(tagStr, tagIdStart, end);
} catch (NumberFormatException e) {
throw new DaxTagParserException("NOT correct DaxTag: " + tagStr, e);
}

// 5. namespace ID resolution logic
int namespaceId ;
if (namespaceSymbol == null || namespaceSymbol.isEmpty()) {
namespaceId = msgnamespaceId ; // config.getAppnamespaceId();
} else {
namespaceId = namespaceMapper.getReferenceId(namespaceSymbol);
}

//6. Is ok return new DaxTag
return namespaceId == DaxCoreConstants.DAXP_NAMESPACE_ID
? DaxTag.createCoreTag(tagId):
DaxTag.of(namespaceId, tagId);
}

public List<DaxTag> parseDaxTagList(String tagListStr, int msgnamespaceId) {
if (tagListStr == null || tagListStr.isEmpty()) {
return new ArrayList<>();
}

List<DaxTag> result = new ArrayList<>();
char separator = DaxCoreConstants.TAG_LIST_SEPARATOR_CHAR;
StringBuilder currentTag = new StringBuilder();

// Iterate through the string character by character (byte-equivalent for UTF-16)
for (int i = 0; i < tagListStr.length(); i++) {
char c = tagListStr.charAt(i);

if (c == separator) {
// When separator is found, process the accumulated token if it's not empty
processAndAddTag(currentTag, result, msgnamespaceId);
currentTag.setLength(0); // Reset the buffer for the next tag
} else {
currentTag.append(c);
}
}

// Don't forget to process the very last tag after the loop ends
processAndAddTag(currentTag, result, msgnamespaceId);

return result;
}

private void processAndAddTag(StringBuilder sb, List<DaxTag> result, int msgnamespaceId) {
// Trim the string manually or use trim() on the string representation
String trimmed = sb.toString().trim();

// Optional: Avoid processing empty strings if your original split omitted them
if (!trimmed.isEmpty()) {
result.add(parseDaxTag(trimmed, msgnamespaceId));
}
}

}