Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private TestResults runTests() throws Exception {
content = Files.readString(file, StandardCharsets.UTF_8);
charContent = content.toCharArray();
} catch (MalformedInputException e) {
LOGGER.warning("UTF-8 failed for " + filename + ", using robust encoding detection");
LOGGER.warning(() -> "UTF-8 failed for " + filename + ", using robust encoding detection");
try {
byte[] rawBytes = Files.readAllBytes(file);
charContent = RobustCharDecoder.decodeToChars(rawBytes, filename);
Expand All @@ -123,7 +123,7 @@ private TestResults runTests() throws Exception {
} catch (JsonParseException e) {
parseSucceeded = false;
} catch (StackOverflowError e) {
LOGGER.warning("StackOverflowError on file: " + filename);
LOGGER.warning(() -> "StackOverflowError on file: " + filename);
parseSucceeded = false; // Treat as parse failure
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ class RobustCharDecoder {
/// @param filename filename for logging purposes
/// @return char array representing the content
static char[] decodeToChars(byte[] rawBytes, String filename) {
LOGGER.fine("Attempting robust decoding for " + filename + " (" + rawBytes.length + " bytes)");
LOGGER.fine(() -> "Attempting robust decoding for " + filename + " (" + rawBytes.length + " bytes)");

// Stage 1: BOM Detection
BomResult bom = detectBOM(rawBytes);
if (bom.encoding != null) {
LOGGER.fine("BOM detected for " + filename + ": " + bom.encoding.name());
LOGGER.fine(() -> "BOM detected for " + filename + ": " + bom.encoding.name());
char[] result = tryDecodeWithCharset(rawBytes, bom.offset, bom.encoding, filename);
if (result != null) {
return result;
Expand All @@ -53,13 +53,13 @@ static char[] decodeToChars(byte[] rawBytes, String filename) {
for (Charset encoding : encodings) {
char[] result = tryDecodeWithCharset(rawBytes, 0, encoding, filename);
if (result != null) {
LOGGER.fine("Successfully decoded " + filename + " using " + encoding.name());
LOGGER.fine(() -> "Successfully decoded " + filename + " using " + encoding.name());
return result;
}
}

// Stage 3: Byte-level conversion with UTF-8 sequence awareness
LOGGER.fine("Using permissive byte-to-char conversion for " + filename);
LOGGER.fine(() -> "Using permissive byte-to-char conversion for " + filename);
return convertBytesToCharsPermissively(rawBytes);
}

Expand Down Expand Up @@ -96,10 +96,10 @@ private static char[] tryDecodeWithCharset(byte[] bytes, int offset, Charset cha
return result;

} catch (CharacterCodingException e) {
LOGGER.fine("Failed to decode " + filename + " with " + charset.name() + ": " + e.getMessage());
LOGGER.fine(() -> "Failed to decode " + filename + " with " + charset.name() + ": " + e.getMessage());
return null;
} catch (Exception e) {
LOGGER.fine("Unexpected error decoding " + filename + " with " + charset.name() + ": " + e.getMessage());
LOGGER.fine(() -> "Unexpected error decoding " + filename + " with " + charset.name() + ": " + e.getMessage());
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private DynamicTest createTest(Path file) {
content = Files.readString(file, StandardCharsets.UTF_8);
charContent = content.toCharArray();
} catch (MalformedInputException e) {
LOGGER.warning("UTF-8 failed for " + filename + ", using robust encoding detection");
LOGGER.warning(() -> "UTF-8 failed for " + filename + ", using robust encoding detection");
try {
byte[] rawBytes = Files.readAllBytes(file);
charContent = RobustCharDecoder.decodeToChars(rawBytes, filename);
Expand Down Expand Up @@ -129,7 +129,7 @@ private void testImplementationDefinedSingle(String description, Runnable parseA
// OK - we rejected it
} catch (StackOverflowError e) {
// OK - acceptable for deeply nested structures
LOGGER.warning("StackOverflowError on implementation-defined: " + description);
LOGGER.warning(() -> "StackOverflowError on implementation-defined: " + description);
} catch (Exception e) {
// NOT OK - unexpected exception type
fail("Unexpected exception for %s: %s", description, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ static String fetchFromUrl(String url) {
/// Discovers all classes in the local JSON API packages
/// @return sorted set of classes from jdk.sandbox.java.util.json and jdk.sandbox.internal.util.json
static Set<Class<?>> discoverLocalJsonClasses() {
LOGGER.info("Starting class discovery for JSON API packages");
LOGGER.info(() -> "Starting class discovery for JSON API packages");
final var classes = new TreeSet<Class<?>>(Comparator.comparing(Class::getName));

// Packages to scan - only public API, not internal implementation
Expand Down Expand Up @@ -122,11 +122,11 @@ static Set<Class<?>> discoverLocalJsonClasses() {
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error scanning package: " + packageName, e);
LOGGER.warning(() -> "ERROR: Error scanning package: " + packageName + " - " + e.getMessage());
}
}

LOGGER.info("Discovered " + classes.size() + " classes in JSON API packages: " +
LOGGER.info(() -> "Discovered " + classes.size() + " classes in JSON API packages: " +
classes.stream().map(Class::getName).sorted().collect(Collectors.joining(", ")));
return Collections.unmodifiableSet(classes);
}
Expand All @@ -151,7 +151,7 @@ static void scanDirectory(java.io.File directory, String packageName, Set<Class<
try {
final var clazz = Class.forName(className);
classes.add(clazz);
LOGGER.info("Found class: " + className);
LOGGER.info(() -> "Found class: " + className);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
LOGGER.fine(() -> "Could not load class: " + className);
}
Expand Down Expand Up @@ -189,15 +189,15 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set<Class<?>> class
try {
final var clazz = Class.forName(className);
classes.add(clazz);
LOGGER.info("Found class in JAR: " + className);
LOGGER.info(() -> "Found class in JAR: " + className);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
LOGGER.fine(() -> "Could not load class from JAR: " + className);
}
}
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error scanning JAR: " + jarUrl, e);
LOGGER.warning(() -> "ERROR: Error scanning JAR: " + jarUrl + " - " + e.getMessage());
}
}

Expand All @@ -206,7 +206,7 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set<Class<?>> class
/// @return map of className to source code (or error message if fetch failed)
static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
Objects.requireNonNull(localClasses, "localClasses must not be null");
LOGGER.info("Fetching upstream sources for " + localClasses.size() + " classes");
LOGGER.info(() -> "Fetching upstream sources for " + localClasses.size() + " classes");

final var results = new LinkedHashMap<String, String>();
final var httpClient = HttpClient.newBuilder()
Expand All @@ -227,7 +227,7 @@ static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
final var upstreamPath = mapToUpstreamPath(className);
final var url = GITHUB_BASE_URL + upstreamPath;

LOGGER.info("Fetching upstream source: " + url);
LOGGER.info(() -> "Fetching upstream source: " + url);

try {
final var request = HttpRequest.newBuilder()
Expand All @@ -242,20 +242,20 @@ static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
final var body = response.body();
FETCH_CACHE.put(className, body);
results.put(className, body);
LOGGER.info("Successfully fetched " + body.length() + " chars for: " + className);
LOGGER.info(() -> "Successfully fetched " + body.length() + " chars for: " + className);
} else if (response.statusCode() == 404) {
final var error = "NOT_FOUND: Upstream file not found (possibly deleted or renamed)";
results.put(className, error);
LOGGER.info("404 Not Found for upstream: " + className + " at " + url);
LOGGER.info(() -> "404 Not Found for upstream: " + className + " at " + url);
} else {
final var error = "HTTP_ERROR: Status " + response.statusCode();
results.put(className, error);
LOGGER.info("HTTP error " + response.statusCode() + " for " + className + " at " + url);
LOGGER.info(() -> "HTTP error " + response.statusCode() + " for " + className + " at " + url);
}
} catch (Exception e) {
final var error = "FETCH_ERROR: " + e.getMessage();
results.put(className, error);
LOGGER.info("Fetch error for " + className + " at " + url + ": " + e.getMessage());
LOGGER.info(() -> "Fetch error for " + className + " at " + url + ": " + e.getMessage());
}
}

Expand Down Expand Up @@ -306,7 +306,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
return JsonObject.of(errorMap);
}

LOGGER.info("Extracting upstream API for: " + className);
LOGGER.info(() -> "Extracting upstream API for: " + className);

final var compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
Expand Down Expand Up @@ -367,7 +367,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
));

} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error parsing upstream source for " + className, e);
LOGGER.warning(() -> "ERROR: Error parsing upstream source for " + className + " - " + e.getMessage());
return JsonObject.of(Map.of(
"error", JsonString.of("Parse error: " + e.getMessage()),
"className", JsonString.of(className)
Expand All @@ -376,7 +376,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
try {
fileManager.close();
} catch (IOException e) {
LOGGER.log(Level.FINE, "Error closing file manager", e);
LOGGER.fine(() -> "Error closing file manager: " + e.getMessage());
}
}
}
Expand Down Expand Up @@ -877,7 +877,7 @@ static String normalizeTypeName(String typeName) {
/// Runs source-to-source comparison for fair parameter name comparison
/// @return complete comparison report as JSON
static JsonObject runFullComparison() {
LOGGER.info("Starting full API comparison");
LOGGER.info(() -> "Starting full API comparison");
final var startTime = Instant.now();

final var reportMap = new LinkedHashMap<String, JsonValue>();
Expand All @@ -887,7 +887,7 @@ static JsonObject runFullComparison() {

// Discover local classes
final var localClasses = discoverLocalJsonClasses();
LOGGER.info("Found " + localClasses.size() + " local classes");
LOGGER.info(() -> "Found " + localClasses.size() + " local classes");

// Extract and compare APIs
final var differences = new ArrayList<JsonValue>();
Expand Down Expand Up @@ -927,7 +927,7 @@ static JsonObject runFullComparison() {
final var duration = Duration.between(startTime, Instant.now());
reportMap.put("durationMs", JsonNumber.of(duration.toMillis()));

LOGGER.info("Comparison completed in " + duration.toMillis() + "ms");
LOGGER.info(() -> "Comparison completed in " + duration.toMillis() + "ms");

return JsonObject.of(reportMap);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public sealed interface JsonSchema
JsonSchema.NotSchema,
JsonSchema.RootRef {

Logger LOG = Logger.getLogger(JsonSchema.class.getName());
Logger LOGGER = Logger.getLogger(JsonSchema.class.getName());

/// Prevents external implementations, ensuring all schema types are inner records
enum Nothing implements JsonSchema {
Expand All @@ -69,10 +69,15 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
/// @param schemaJson JSON Schema document as JsonValue
/// @return Immutable JsonSchema instance
/// @throws IllegalArgumentException if schema is invalid
static JsonSchema compile(JsonValue schemaJson) {
Objects.requireNonNull(schemaJson, "schemaJson");
return SchemaCompiler.compile(schemaJson);
}
static JsonSchema compile(JsonValue schemaJson) {
Objects.requireNonNull(schemaJson, "schemaJson");
try {
return SchemaCompiler.compile(schemaJson);
} catch (IllegalArgumentException e) {
LOGGER.severe(() -> "ERROR: Failed to compile JSON schema: " + e.getMessage());
throw e;
}
}

/// Validates JSON document against this schema
///
Expand All @@ -84,15 +89,30 @@ default ValidationResult validate(JsonValue json) {
Deque<ValidationFrame> stack = new ArrayDeque<>();
stack.push(new ValidationFrame("", this, json));

final int stackDepthWarningThreshold = 1000;
final java.util.concurrent.atomic.AtomicInteger maxStackDepth = new java.util.concurrent.atomic.AtomicInteger(0);
Comment on lines +92 to +93
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AtomicInteger for tracking stack depth introduces unnecessary atomic operations overhead in single-threaded validation. Consider using a simple int variable instead since validation is not performed concurrently.

Copilot uses AI. Check for mistakes.

while (!stack.isEmpty()) {
ValidationFrame frame = stack.pop();
LOG.finest(() -> "POP " + frame.path() +
int currentStackSize = stack.size();
maxStackDepth.updateAndGet(current -> Math.max(current, currentStackSize));
Comment on lines +97 to +98
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateAndGet operation with lambda is called on every validation frame pop, creating performance overhead. Since this is single-threaded, use a simple Math.max assignment with a primitive int variable.

Copilot uses AI. Check for mistakes.

if (currentStackSize > stackDepthWarningThreshold) {
LOGGER.fine(() -> "PERFORMANCE WARNING: Validation stack depth exceeds recommended threshold: " + currentStackSize);
Comment on lines +94 to +101
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This warning will be logged repeatedly for every frame when the stack exceeds the threshold, potentially creating log spam. Consider logging this warning only once per validation or implementing a throttling mechanism.

Copilot uses AI. Check for mistakes.
}

LOGGER.finest(() -> "POP " + frame.path() +
" schema=" + frame.schema().getClass().getSimpleName());
ValidationResult result = frame.schema.validateAt(frame.path, frame.json, stack);
if (!result.valid()) {
errors.addAll(result.errors());
}
}

final int finalMaxStackDepth = maxStackDepth.get();
if (finalMaxStackDepth > 100) {
LOGGER.fine(() -> "PERFORMANCE WARNING: Maximum validation stack depth reached: " + finalMaxStackDepth);
}
Comment on lines +112 to +115
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 100 should be extracted as a named constant. Also, consider using the same threshold variable (stackDepthWarningThreshold = 1000) for consistency, or clearly document why different thresholds are needed.

Copilot uses AI. Check for mistakes.

return errors.isEmpty() ? ValidationResult.success() : ValidationResult.failure(errors);
}
Expand Down Expand Up @@ -368,7 +388,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
Deque<ValidationFrame> branchStack = new ArrayDeque<>();
List<ValidationError> branchErrors = new ArrayList<>();

LOG.finest(() -> "BRANCH START: " + schema.getClass().getSimpleName());
LOGGER.finest(() -> "BRANCH START: " + schema.getClass().getSimpleName());
branchStack.push(new ValidationFrame(path, schema, json));

while (!branchStack.isEmpty()) {
Expand All @@ -384,7 +404,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
break;
}
collected.addAll(branchErrors);
LOG.finest(() -> "BRANCH END: " + branchErrors.size() + " errors");
LOGGER.finest(() -> "BRANCH END: " + branchErrors.size() + " errors");
}

return anyValid ? ValidationResult.success() : ValidationResult.failure(collected);
Expand All @@ -401,7 +421,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
// Step 2 - choose branch
JsonSchema branch = ifResult.valid() ? thenSchema : elseSchema;

LOG.finer(() -> String.format(
LOGGER.finer(() -> String.format(
"Conditional path=%s ifValid=%b branch=%s",
path, ifResult.valid(),
branch == null ? "none" : (ifResult.valid() ? "then" : "else")));
Expand Down Expand Up @@ -439,8 +459,8 @@ final class SchemaCompiler {
private static JsonSchema currentRootSchema;

private static void trace(String stage, JsonValue fragment) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer(() ->
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer(() ->
String.format("[%s] %s", stage, fragment.toString()));
}
}
Expand All @@ -460,6 +480,7 @@ private static JsonSchema compileInternal(JsonValue schemaJson) {
}

if (!(schemaJson instanceof JsonObject obj)) {
LOGGER.severe(() -> "ERROR: Schema must be an object or boolean, got: " + schemaJson.getClass().getSimpleName());
throw new IllegalArgumentException("Schema must be an object or boolean");
}

Expand All @@ -483,6 +504,7 @@ private static JsonSchema compileInternal(JsonValue schemaJson) {
}
JsonSchema resolved = definitions.get(ref);
if (resolved == null) {
LOGGER.severe(() -> "ERROR: Unresolved $ref: " + ref);
throw new IllegalArgumentException("Unresolved $ref: " + ref);
}
return resolved;
Expand Down