diff --git a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java index aeab23a..4aad5f9 100644 --- a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java +++ b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java @@ -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); @@ -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 } diff --git a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java index deb4607..a7eb308 100644 --- a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java +++ b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java @@ -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; @@ -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); } @@ -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; } } diff --git a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java b/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java index 88a04bc..263070f 100644 --- a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java +++ b/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java @@ -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); @@ -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); diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java index 2596c39..df18509 100644 --- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java +++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java @@ -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> discoverLocalJsonClasses() { - LOGGER.info("Starting class discovery for JSON API packages"); + LOGGER.info(() -> "Starting class discovery for JSON API packages"); final var classes = new TreeSet>(Comparator.comparing(Class::getName)); // Packages to scan - only public API, not internal implementation @@ -122,11 +122,11 @@ static Set> 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); } @@ -151,7 +151,7 @@ static void scanDirectory(java.io.File directory, String packageName, Set "Found class: " + className); } catch (ClassNotFoundException | NoClassDefFoundError e) { LOGGER.fine(() -> "Could not load class: " + className); } @@ -189,7 +189,7 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set> 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); } @@ -197,7 +197,7 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set> class } } } catch (Exception e) { - LOGGER.log(Level.WARNING, "Error scanning JAR: " + jarUrl, e); + LOGGER.warning(() -> "ERROR: Error scanning JAR: " + jarUrl + " - " + e.getMessage()); } } @@ -206,7 +206,7 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set> class /// @return map of className to source code (or error message if fetch failed) static Map fetchUpstreamSources(Set> 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(); final var httpClient = HttpClient.newBuilder() @@ -227,7 +227,7 @@ static Map fetchUpstreamSources(Set> 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() @@ -242,20 +242,20 @@ static Map fetchUpstreamSources(Set> 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()); } } @@ -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) { @@ -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) @@ -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()); } } } @@ -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(); @@ -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(); @@ -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); } diff --git a/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java b/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java index 3c3c7ad..bec6e62 100644 --- a/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java +++ b/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java @@ -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 { @@ -69,10 +69,15 @@ public ValidationResult validateAt(String path, JsonValue json, Deque "ERROR: Failed to compile JSON schema: " + e.getMessage()); + throw e; + } + } /// Validates JSON document against this schema /// @@ -84,15 +89,30 @@ default ValidationResult validate(JsonValue json) { Deque 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); + while (!stack.isEmpty()) { ValidationFrame frame = stack.pop(); - LOG.finest(() -> "POP " + frame.path() + + int currentStackSize = stack.size(); + maxStackDepth.updateAndGet(current -> Math.max(current, currentStackSize)); + + if (currentStackSize > stackDepthWarningThreshold) { + LOGGER.fine(() -> "PERFORMANCE WARNING: Validation stack depth exceeds recommended threshold: " + currentStackSize); + } + + 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); + } return errors.isEmpty() ? ValidationResult.success() : ValidationResult.failure(errors); } @@ -368,7 +388,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque branchStack = new ArrayDeque<>(); List 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()) { @@ -384,7 +404,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque "BRANCH END: " + branchErrors.size() + " errors"); + LOGGER.finest(() -> "BRANCH END: " + branchErrors.size() + " errors"); } return anyValid ? ValidationResult.success() : ValidationResult.failure(collected); @@ -401,7 +421,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque String.format( + LOGGER.finer(() -> String.format( "Conditional path=%s ifValid=%b branch=%s", path, ifResult.valid(), branch == null ? "none" : (ifResult.valid() ? "then" : "else"))); @@ -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())); } } @@ -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"); } @@ -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;