diff --git a/json-java21-api-tracker/pom.xml b/json-java21-api-tracker/pom.xml
index eb03e82..9f631ac 100644
--- a/json-java21-api-tracker/pom.xml
+++ b/json-java21-api-tracker/pom.xml
@@ -43,39 +43,8 @@
-
-
- relaxed
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- -Xlint:all
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- ${maven.compiler.release}
-
- --enable-preview
- -Xlint:all
- -Werror
-
-
-
org.apache.maven.plugins
maven-surefire-plugin
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 13b8c33..2596c39 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
@@ -15,25 +15,15 @@
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
-import java.util.ArrayList;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.tools.DiagnosticCollector;
-import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
-import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import com.sun.source.tree.ClassTree;
@@ -45,7 +35,7 @@
import com.sun.source.util.TreePathScanner;
/// API Tracker module for comparing local and upstream JSON APIs
-///
+///
/// This module provides functionality to:
/// - Discover local JSON API classes via reflection
/// - Fetch corresponding upstream sources from GitHub
@@ -55,40 +45,40 @@
/// Modular design supports different extraction strategies:
/// - Binary reflection for quick class introspection
/// - Source parsing for accurate parameter names and signatures
-///
+///
/// All functionality is exposed as static methods following functional programming principles
public sealed interface ApiTracker permits ApiTracker.Nothing {
-
+
/// Local source root for source-based extraction
- static final String LOCAL_SOURCE_ROOT = "json-java21/src/main/java";
-
+ String LOCAL_SOURCE_ROOT = "json-java21/src/main/java";
+
/// Empty enum to seal the interface - no instances allowed
enum Nothing implements ApiTracker {}
-
+
// Package-private logger shared across the module
- static final Logger LOGGER = Logger.getLogger(ApiTracker.class.getName());
-
+ Logger LOGGER = Logger.getLogger(ApiTracker.class.getName());
+
// Cache for HTTP responses to avoid repeated fetches
- static final Map FETCH_CACHE = new ConcurrentHashMap<>();
-
+ Map FETCH_CACHE = new ConcurrentHashMap<>();
+
// GitHub base URL for upstream sources
- static final String GITHUB_BASE_URL = "https://raw.githubusercontent.com/openjdk/jdk-sandbox/refs/heads/json/src/java.base/share/classes/";
-
+ String GITHUB_BASE_URL = "https://raw.githubusercontent.com/openjdk/jdk-sandbox/refs/heads/json/src/java.base/share/classes/";
+
/// Fetches content from a URL
static String fetchFromUrl(String url) {
final var httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
-
+
try {
final var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
-
+
final var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
-
+
if (response.statusCode() == 200) {
return response.body();
} else if (response.statusCode() == 404) {
@@ -100,29 +90,29 @@ static String fetchFromUrl(String url) {
return "FETCH_ERROR: " + e.getMessage();
}
}
-
+
/// 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");
- final var classes = new TreeSet>((a, b) -> a.getName().compareTo(b.getName()));
-
+ final var classes = new TreeSet>(Comparator.comparing(Class::getName));
+
// Packages to scan - only public API, not internal implementation
final var packages = List.of(
"jdk.sandbox.java.util.json"
);
-
+
final var classLoader = Thread.currentThread().getContextClassLoader();
-
+
for (final var packageName : packages) {
try {
final var path = packageName.replace('.', '/');
final var resources = classLoader.getResources(path);
-
+
while (resources.hasMoreElements()) {
final var url = resources.nextElement();
LOGGER.fine(() -> "Scanning resource: " + url);
-
+
if ("file".equals(url.getProtocol())) {
// Handle directory scanning
scanDirectory(new java.io.File(url.toURI()), packageName, classes);
@@ -135,28 +125,28 @@ static Set> discoverLocalJsonClasses() {
LOGGER.log(Level.WARNING, "Error scanning package: " + packageName, e);
}
}
-
- 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);
}
-
+
/// Scans a directory for class files
static void scanDirectory(java.io.File directory, String packageName, Set> classes) {
if (!directory.exists() || !directory.isDirectory()) {
return;
}
-
+
final var files = directory.listFiles();
if (files == null) {
return;
}
-
+
for (final var file : files) {
if (file.isDirectory()) {
scanDirectory(file, packageName + "." + file.getName(), classes);
} else if (file.getName().endsWith(".class") && !file.getName().contains("$")) {
- final var className = packageName + '.' +
+ final var className = packageName + '.' +
file.getName().substring(0, file.getName().length() - 6);
try {
final var clazz = Class.forName(className);
@@ -168,7 +158,7 @@ static void scanDirectory(java.io.File directory, String packageName, Set> classes) {
try {
@@ -177,25 +167,25 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set> class
if (exclamation < 0) {
return;
}
-
+
final var jarFilePath = jarPath.substring(5, exclamation); // Remove "file:"
final var packagePath = packageName.replace('.', '/');
-
+
try (final var jarFile = new java.util.jar.JarFile(jarFilePath)) {
final var entries = jarFile.entries();
-
+
while (entries.hasMoreElements()) {
final var entry = entries.nextElement();
final var entryName = entry.getName();
-
- if (entryName.startsWith(packagePath) &&
- entryName.endsWith(".class") &&
+
+ if (entryName.startsWith(packagePath) &&
+ entryName.endsWith(".class") &&
!entryName.contains("$")) {
-
+
final var className = entryName
.substring(0, entryName.length() - 6)
.replace('/', '.');
-
+
try {
final var clazz = Class.forName(className);
classes.add(clazz);
@@ -210,44 +200,44 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set> class
LOGGER.log(Level.WARNING, "Error scanning JAR: " + jarUrl, e);
}
}
-
+
/// Fetches upstream source files from GitHub for the given local classes
/// @param localClasses set of local classes to fetch upstream sources for
/// @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");
-
+
final var results = new LinkedHashMap();
final var httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
-
+
for (final var clazz : localClasses) {
final var className = clazz.getName();
final var cachedSource = FETCH_CACHE.get(className);
-
+
if (cachedSource != null) {
LOGGER.fine(() -> "Using cached source for: " + className);
results.put(className, cachedSource);
continue;
}
-
+
// Map package name from jdk.sandbox.* to standard java.*
final var upstreamPath = mapToUpstreamPath(className);
final var url = GITHUB_BASE_URL + upstreamPath;
-
+
LOGGER.info("Fetching upstream source: " + url);
-
+
try {
final var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
-
+
final var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
-
+
if (response.statusCode() == 200) {
final var body = response.body();
FETCH_CACHE.put(className, body);
@@ -268,10 +258,10 @@ static Map fetchUpstreamSources(Set> localClasses) {
LOGGER.info("Fetch error for " + className + " at " + url + ": " + e.getMessage());
}
}
-
+
return Collections.unmodifiableMap(results);
}
-
+
/// Maps local class name to upstream GitHub path
static String mapToUpstreamPath(String className) {
// Remove jdk.sandbox prefix and map to standard packages
@@ -279,10 +269,10 @@ static String mapToUpstreamPath(String className) {
.replace("jdk.sandbox.java.util.json", "java/util/json")
.replace("jdk.sandbox.internal.util.json", "jdk/internal/util/json")
.replace('.', '/');
-
+
return path + ".java";
}
-
+
/// Extracts local API from source file
static JsonObject extractLocalApiFromSource(String className) {
final var path = LOCAL_SOURCE_ROOT + "/" + className.replace('.', '/') + ".java";
@@ -296,7 +286,7 @@ static JsonObject extractLocalApiFromSource(String className) {
));
}
}
-
+
/// Extracts public API from source code using compiler parsing
/// @param sourceCode the source code to parse
/// @param className the expected class name
@@ -304,10 +294,10 @@ static JsonObject extractLocalApiFromSource(String className) {
static JsonObject extractApiFromSource(String sourceCode, String className) {
Objects.requireNonNull(sourceCode, "sourceCode must not be null");
Objects.requireNonNull(className, "className must not be null");
-
+
// Check for fetch errors
- if (sourceCode.startsWith("NOT_FOUND:") ||
- sourceCode.startsWith("HTTP_ERROR:") ||
+ if (sourceCode.startsWith("NOT_FOUND:") ||
+ sourceCode.startsWith("HTTP_ERROR:") ||
sourceCode.startsWith("FETCH_ERROR:")) {
final var errorMap = Map.of(
"error", JsonString.of(sourceCode),
@@ -315,9 +305,9 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
);
return JsonObject.of(errorMap);
}
-
+
LOGGER.info("Extracting upstream API for: " + className);
-
+
final var compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
return JsonObject.of(Map.of(
@@ -325,21 +315,21 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
"className", JsonString.of(className)
));
}
-
+
final var diagnostics = new DiagnosticCollector();
final var fileManager = compiler.getStandardFileManager(diagnostics, null, StandardCharsets.UTF_8);
-
+
try {
// Extract simple class name from fully qualified name
final var simpleClassName = className.substring(className.lastIndexOf('.') + 1);
-
+
// Create compilation units
final var compilationUnits = new ArrayList();
compilationUnits.add(new InMemoryJavaFileObject(className, sourceCode));
-
+
// Add minimal stubs for common dependencies
addCommonStubs(compilationUnits);
-
+
// Parse-only compilation with relaxed settings
final var options = List.of(
"-proc:none",
@@ -348,7 +338,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
"--enable-preview",
"--release", "24"
);
-
+
final var task = (JavacTask) compiler.getTask(
null,
fileManager,
@@ -357,9 +347,9 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
null,
compilationUnits
);
-
+
final var trees = task.parse();
-
+
// Extract API using visitor
for (final var tree : trees) {
final var fileName = tree.getSourceFile().getName();
@@ -369,13 +359,13 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
return visitor.getExtractedApi();
}
}
-
+
// If we get here, parsing failed
return JsonObject.of(Map.of(
"error", JsonString.of("Failed to parse source"),
"className", JsonString.of(className)
));
-
+
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error parsing upstream source for " + className, e);
return JsonObject.of(Map.of(
@@ -390,7 +380,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
}
}
}
-
+
/// Adds common stub dependencies for JSON API parsing
static void addCommonStubs(List compilationUnits) {
// PreviewFeature annotation stub
@@ -404,20 +394,20 @@ static void addCommonStubs(List compilationUnits) {
enum Feature { JSON }
}
"""));
-
+
// JsonValue base interface stub
compilationUnits.add(new InMemoryJavaFileObject("java.util.json.JsonValue", """
package java.util.json;
public sealed interface JsonValue permits JsonObject, JsonArray, JsonString, JsonNumber, JsonBoolean, JsonNull {}
"""));
-
+
// Basic JSON type stubs
final var jsonTypes = List.of("JsonObject", "JsonArray", "JsonString", "JsonNumber", "JsonBoolean", "JsonNull");
for (final var type : jsonTypes) {
compilationUnits.add(new InMemoryJavaFileObject("java.util.json." + type,
"package java.util.json; public non-sealed interface " + type + " extends JsonValue {}"));
}
-
+
// Internal implementation stubs
compilationUnits.add(new InMemoryJavaFileObject("jdk.internal.util.json.JsonObjectImpl", """
package jdk.internal.util.json;
@@ -432,9 +422,9 @@ public JsonObjectImpl(Map map) {}
}
"""));
}
-
+
/// In-memory JavaFileObject for creating stub classes
- static class InMemoryJavaFileObject extends SimpleJavaFileObject {
+ class InMemoryJavaFileObject extends SimpleJavaFileObject {
private final String content;
InMemoryJavaFileObject(String className, String content) {
@@ -447,9 +437,9 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
-
+
/// Visitor to extract API information from AST
- static class ApiExtractorVisitor extends TreePathScanner {
+ class ApiExtractorVisitor extends TreePathScanner {
private final Map apiMap = new LinkedHashMap<>();
private final Map methodsMap = new LinkedHashMap<>();
private final Map fieldsMap = new LinkedHashMap<>();
@@ -467,13 +457,13 @@ public Void visitClass(ClassTree node, Void p) {
// Basic class information
apiMap.put("className", JsonString.of(node.getSimpleName().toString()));
apiMap.put("modifiers", extractTreeModifiers(node.getModifiers()));
-
+
// Type information
final var kind = node.getKind();
apiMap.put("isInterface", JsonBoolean.of(kind == Tree.Kind.INTERFACE));
apiMap.put("isEnum", JsonBoolean.of(kind == Tree.Kind.ENUM));
apiMap.put("isRecord", JsonBoolean.of(kind == Tree.Kind.RECORD));
-
+
// Package name (from compilation unit)
final var compilationUnit = getCurrentPath().getCompilationUnit();
final var packageTree = compilationUnit.getPackage();
@@ -482,13 +472,13 @@ public Void visitClass(ClassTree node, Void p) {
} else {
apiMap.put("packageName", JsonString.of(""));
}
-
+
// Check if sealed
final var modifiers = node.getModifiers();
final var isSealed = modifiers.getFlags().stream()
.anyMatch(m -> m.toString().equals("SEALED"));
apiMap.put("isSealed", JsonBoolean.of(isSealed));
-
+
// Inheritance
final var superTypes = new ArrayList();
if (node.getExtendsClause() != null) {
@@ -498,12 +488,12 @@ public Void visitClass(ClassTree node, Void p) {
.map(tree -> JsonString.of(extractSimpleName(tree.toString())))
.forEach(superTypes::add);
apiMap.put("extends", JsonArray.of(superTypes));
-
+
// Permitted subclasses (approximation - would need full symbol resolution)
if (isSealed) {
apiMap.put("permits", JsonArray.of(List.of()));
}
-
+
return super.visitClass(node, p);
}
@@ -511,7 +501,7 @@ public Void visitClass(ClassTree node, Void p) {
public Void visitMethod(MethodTree node, Void p) {
// Check if public
final var isPublic = isPublicMember(node.getModifiers());
-
+
if (isPublic) {
final var methodInfo = new LinkedHashMap();
methodInfo.put("modifiers", extractTreeModifiers(node.getModifiers()));
@@ -519,17 +509,17 @@ public Void visitMethod(MethodTree node, Void p) {
node.getReturnType() != null ? node.getReturnType().toString() : "void")));
methodInfo.put("genericReturnType", JsonString.of(
node.getReturnType() != null ? node.getReturnType().toString() : "void"));
-
+
final var params = node.getParameters().stream()
.map(param -> JsonString.of(extractSimpleName(param.getType().toString()) + " " + param.getName()))
.collect(Collectors.toList());
methodInfo.put("parameters", JsonArray.of(params));
-
+
final var exceptions = node.getThrows().stream()
.map(ex -> JsonString.of(extractSimpleName(ex.toString())))
.collect(Collectors.toList());
methodInfo.put("throws", JsonArray.of(exceptions));
-
+
// Handle constructors separately
if (node.getName().toString().equals("")) {
constructors.add(JsonObject.of(methodInfo));
@@ -537,36 +527,36 @@ public Void visitMethod(MethodTree node, Void p) {
methodsMap.put(node.getName().toString(), JsonObject.of(methodInfo));
}
}
-
+
return super.visitMethod(node, p);
}
- @Override
+ @Override
public Void visitVariable(VariableTree node, Void p) {
// Only process fields (not method parameters or local variables)
if (getCurrentPath().getParentPath().getLeaf().getKind() == Tree.Kind.CLASS) {
final var isPublic = isPublicMember(node.getModifiers());
-
+
if (isPublic) {
final var fieldInfo = new LinkedHashMap();
fieldInfo.put("modifiers", extractTreeModifiers(node.getModifiers()));
fieldInfo.put("type", JsonString.of(extractSimpleName(node.getType().toString())));
fieldInfo.put("genericType", JsonString.of(node.getType().toString()));
-
+
fieldsMap.put(node.getName().toString(), JsonObject.of(fieldInfo));
}
}
-
+
return super.visitVariable(node, p);
}
-
+
private JsonArray extractTreeModifiers(ModifiersTree modifiers) {
final var modList = modifiers.getFlags().stream()
.map(m -> JsonString.of(m.toString().toLowerCase()))
.collect(Collectors.toList());
return JsonArray.of(modList);
}
-
+
private boolean isPublicMember(ModifiersTree modifiers) {
// In interfaces, methods without private/default are implicitly public
final var parent = getCurrentPath().getParentPath();
@@ -576,7 +566,7 @@ private boolean isPublicMember(ModifiersTree modifiers) {
}
return modifiers.getFlags().contains(javax.lang.model.element.Modifier.PUBLIC);
}
-
+
private String extractSimpleName(String typeName) {
// Remove generic parameters and package prefixes
var name = typeName;
@@ -591,7 +581,7 @@ private String extractSimpleName(String typeName) {
return name;
}
}
-
+
/// Compares local and upstream APIs to identify differences
/// @param local the local API structure
/// @param upstream the upstream API structure
@@ -599,23 +589,23 @@ private String extractSimpleName(String typeName) {
static JsonObject compareApis(JsonObject local, JsonObject upstream) {
Objects.requireNonNull(local, "local must not be null");
Objects.requireNonNull(upstream, "upstream must not be null");
-
+
final var diffMap = new LinkedHashMap();
-
+
// Extract class name safely
final var localClassName = local.members().get("className");
- final var className = localClassName instanceof JsonString js ?
+ final var className = localClassName instanceof JsonString js ?
js.value() : "Unknown";
-
+
diffMap.put("className", JsonString.of(className));
-
+
// Check for upstream errors
if (upstream.members().containsKey("error")) {
diffMap.put("status", JsonString.of("UPSTREAM_ERROR"));
diffMap.put("error", upstream.members().get("error"));
return JsonObject.of(diffMap);
}
-
+
// Check if status is NOT_IMPLEMENTED (from parsing)
if (upstream.members().containsKey("status")) {
final var status = ((JsonString) upstream.members().get("status")).value();
@@ -624,32 +614,32 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
return JsonObject.of(diffMap);
}
}
-
+
// Perform detailed comparison
final var differences = new ArrayList();
var hasChanges = false;
-
+
// Compare basic class attributes
hasChanges |= compareAttribute("isInterface", local, upstream, differences);
hasChanges |= compareAttribute("isEnum", local, upstream, differences);
hasChanges |= compareAttribute("isRecord", local, upstream, differences);
hasChanges |= compareAttribute("isSealed", local, upstream, differences);
-
+
// Compare modifiers
hasChanges |= compareModifiers(local, upstream, differences);
-
+
// Compare inheritance
hasChanges |= compareInheritance(local, upstream, differences);
-
+
// Compare methods
hasChanges |= compareMethods(local, upstream, differences);
-
+
// Compare fields
hasChanges |= compareFields(local, upstream, differences);
-
+
// Compare constructors
hasChanges |= compareConstructors(local, upstream, differences);
-
+
// Set status based on findings
if (!hasChanges) {
diffMap.put("status", JsonString.of("MATCHING"));
@@ -657,15 +647,15 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
diffMap.put("status", JsonString.of("DIFFERENT"));
diffMap.put("differences", JsonArray.of(differences));
}
-
+
return JsonObject.of(diffMap);
}
-
+
/// Compares a simple boolean attribute
static boolean compareAttribute(String attrName, JsonObject local, JsonObject upstream, List differences) {
final var localValue = local.members().get(attrName);
final var upstreamValue = upstream.members().get(attrName);
-
+
if (!Objects.equals(localValue, upstreamValue)) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("attributeChanged"),
@@ -677,23 +667,23 @@ static boolean compareAttribute(String attrName, JsonObject local, JsonObject up
}
return false;
}
-
+
/// Compares class modifiers
static boolean compareModifiers(JsonObject local, JsonObject upstream, List differences) {
final var localMods = (JsonArray) local.members().get("modifiers");
final var upstreamMods = (JsonArray) upstream.members().get("modifiers");
-
+
if (localMods == null || upstreamMods == null) {
return false;
}
-
+
final var localSet = localMods.values().stream()
.map(v -> ((JsonString) v).value())
.collect(Collectors.toSet());
final var upstreamSet = upstreamMods.values().stream()
.map(v -> ((JsonString) v).value())
.collect(Collectors.toSet());
-
+
if (!localSet.equals(upstreamSet)) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("modifiersChanged"),
@@ -704,23 +694,23 @@ static boolean compareModifiers(JsonObject local, JsonObject upstream, List differences) {
final var localExtends = (JsonArray) local.members().get("extends");
final var upstreamExtends = (JsonArray) upstream.members().get("extends");
-
+
if (localExtends == null || upstreamExtends == null) {
return false;
}
-
+
final var localTypes = localExtends.values().stream()
.map(v -> normalizeTypeName(((JsonString) v).value()))
.collect(Collectors.toSet());
final var upstreamTypes = upstreamExtends.values().stream()
.map(v -> normalizeTypeName(((JsonString) v).value()))
.collect(Collectors.toSet());
-
+
if (!localTypes.equals(upstreamTypes)) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("inheritanceChanged"),
@@ -731,18 +721,18 @@ static boolean compareInheritance(JsonObject local, JsonObject upstream, List differences) {
final var localMethods = (JsonObject) local.members().get("methods");
final var upstreamMethods = (JsonObject) upstream.members().get("methods");
-
+
if (localMethods == null || upstreamMethods == null) {
return false;
}
-
+
var hasChanges = false;
-
+
// Check for removed methods (in local but not upstream)
for (final var entry : localMethods.members().entrySet()) {
if (!upstreamMethods.members().containsKey(entry.getKey())) {
@@ -754,7 +744,7 @@ static boolean compareMethods(JsonObject local, JsonObject upstream, List differences) {
final var localFields = (JsonObject) local.members().get("fields");
final var upstreamFields = (JsonObject) upstream.members().get("fields");
-
+
if (localFields == null || upstreamFields == null) {
return false;
}
-
+
var hasChanges = false;
-
+
// Check for field differences
final var localFieldNames = localFields.members().keySet();
final var upstreamFieldNames = upstreamFields.members().keySet();
-
+
if (!localFieldNames.equals(upstreamFieldNames)) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("fieldsChanged"),
@@ -841,70 +831,70 @@ static boolean compareFields(JsonObject local, JsonObject upstream, List differences) {
- final var localCtors = (JsonArray) local.members().get("constructors");
- final var upstreamCtors = (JsonArray) upstream.members().get("constructors");
-
- if (localCtors == null || upstreamCtors == null) {
+ final var localConstructors = (JsonArray) local.members().get("constructors");
+ final var upstreamConstructors = (JsonArray) upstream.members().get("constructors");
+
+ if (localConstructors == null || upstreamConstructors == null) {
return false;
}
-
- if (localCtors.values().size() != upstreamCtors.values().size()) {
+
+ if (localConstructors.values().size() != upstreamConstructors.values().size()) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("constructorsChanged"),
- "localCount", JsonNumber.of(localCtors.values().size()),
- "upstreamCount", JsonNumber.of(upstreamCtors.values().size())
+ "localCount", JsonNumber.of(localConstructors.values().size()),
+ "upstreamCount", JsonNumber.of(upstreamConstructors.values().size())
)));
return true;
}
-
+
return false;
}
-
+
/// Normalizes type names by removing package prefixes
static String normalizeTypeName(String typeName) {
// Handle generic types
var normalized = typeName;
-
+
// Replace jdk.sandbox.* with standard packages
normalized = normalized.replace("jdk.sandbox.java.util.json", "java.util.json");
normalized = normalized.replace("jdk.sandbox.internal.util.json", "jdk.internal.util.json");
-
+
// Remove any remaining package prefixes for comparison
if (normalized.contains(".")) {
final var parts = normalized.split("\\.");
normalized = parts[parts.length - 1];
}
-
+
return normalized;
}
-
+
/// 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");
final var startTime = Instant.now();
-
+
final var reportMap = new LinkedHashMap();
reportMap.put("timestamp", JsonString.of(startTime.toString()));
reportMap.put("localPackage", JsonString.of("jdk.sandbox.java.util.json"));
reportMap.put("upstreamPackage", JsonString.of("java.util.json"));
-
+
// Discover local classes
final var localClasses = discoverLocalJsonClasses();
LOGGER.info("Found " + localClasses.size() + " local classes");
-
+
// Extract and compare APIs
final var differences = new ArrayList();
var matchingCount = 0;
var missingUpstream = 0;
var differentApi = 0;
-
+
for (final var clazz : localClasses) {
final var className = clazz.getName();
final var localApi = extractLocalApiFromSource(className);
@@ -912,7 +902,7 @@ static JsonObject runFullComparison() {
final var upstreamApi = extractApiFromSource(upstreamSource, className);
final var diff = compareApis(localApi, upstreamApi);
differences.add(diff);
-
+
// Count statistics
final var status = ((JsonString) diff.members().get("status")).value();
switch (status) {
@@ -921,7 +911,7 @@ static JsonObject runFullComparison() {
case "DIFFERENT" -> differentApi++;
}
}
-
+
// Build summary
final var summary = JsonObject.of(Map.of(
"totalClasses", JsonNumber.of(localClasses.size()),
@@ -929,26 +919,26 @@ static JsonObject runFullComparison() {
"missingUpstream", JsonNumber.of(missingUpstream),
"differentApi", JsonNumber.of(differentApi)
));
-
+
reportMap.put("summary", summary);
reportMap.put("differences", JsonArray.of(differences));
-
-
+
+
final var duration = Duration.between(startTime, Instant.now());
reportMap.put("durationMs", JsonNumber.of(duration.toMillis()));
-
+
LOGGER.info("Comparison completed in " + duration.toMillis() + "ms");
-
+
return JsonObject.of(reportMap);
}
-
+
/// Fetches single upstream source file
static String fetchUpstreamSource(String className) {
final var cached = FETCH_CACHE.get(className);
if (cached != null) {
return cached;
}
-
+
final var upstreamPath = mapToUpstreamPath(className);
final var url = GITHUB_BASE_URL + upstreamPath;
final var source = fetchFromUrl(url);
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
index 111c794..aa140b7 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
@@ -1,65 +1,68 @@
package io.github.simbo1905.tracker;
import jdk.sandbox.java.util.json.Json;
+
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/// Command-line runner for the API Tracker
-///
+///
/// Usage: java io.github.simbo1905.tracker.ApiTrackerRunner [loglevel] [mode] [sourcepath]
-///
+///
/// Arguments:
/// - loglevel: SEVERE, WARNING, INFO, FINE, FINER, FINEST (default: INFO)
/// - mode: binary|source (default: binary)
/// - binary: Compare binary reflection (local) vs source parsing (remote)
/// - source: Compare source parsing (local) vs source parsing (remote) for accurate parameter names
/// - sourcepath: Path to local source files (required for source mode)
+@SuppressWarnings("JavadocReference")
public class ApiTrackerRunner {
-
- public static void main(String[] args) {
- // Parse command line arguments
- final var logLevel = args.length > 0 ? Level.parse(args[0].toUpperCase()) : Level.INFO;
- final var mode = args.length > 1 ? args[1].toLowerCase() : "binary";
- final var sourcePath = args.length > 2 ? args[2] : null;
-
- configureLogging(logLevel);
-
- System.out.println("=== JSON API Tracker ===");
- System.out.println("Comparing local jdk.sandbox.java.util.json with upstream java.util.json");
- System.out.println("Log level: " + logLevel);
- System.out.println("Mode: " + mode);
- if (sourcePath != null) {
- System.out.println("Local source path: " + sourcePath);
- }
- System.out.println();
-
- try {
- // Run comparison - now only source-to-source for fair parameter comparison
- System.out.println("Running source-to-source comparison for fair parameter names");
- final var report = ApiTracker.runFullComparison();
-
- // Pretty print the report
- System.out.println("=== Comparison Report ===");
- System.out.println(Json.toDisplayString(report, 2));
-
- } catch (Exception e) {
- System.err.println("Error during comparison: " + e.getMessage());
- e.printStackTrace();
- System.exit(1);
- }
+
+ public static void main(String[] args) {
+ // Parse command line arguments
+ final var logLevel = args.length > 0 ? Level.parse(args[0].toUpperCase()) : Level.INFO;
+ final var mode = args.length > 1 ? args[1].toLowerCase() : "binary";
+ final var sourcePath = args.length > 2 ? args[2] : null;
+
+ configureLogging(logLevel);
+
+ System.out.println("=== JSON API Tracker ===");
+ System.out.println("Comparing local jdk.sandbox.java.util.json with upstream java.util.json");
+ System.out.println("Log level: " + logLevel);
+ System.out.println("Mode: " + mode);
+ if (sourcePath != null) {
+ System.out.println("Local source path: " + sourcePath);
+ }
+ System.out.println();
+
+ try {
+ // Run comparison - now only source-to-source for fair parameter comparison
+ System.out.println("Running source-to-source comparison for fair parameter names");
+ final var report = ApiTracker.runFullComparison();
+
+ // Pretty print the report
+ System.out.println("=== Comparison Report ===");
+ System.out.println(Json.toDisplayString(report, 2));
+
+ } catch (Exception e) {
+ System.err.println("Error during comparison: " + e.getMessage());
+ //noinspection CallToPrintStackTrace
+ e.printStackTrace();
+ System.exit(1);
}
-
- private static void configureLogging(Level level) {
- // Get root logger
- final var rootLogger = Logger.getLogger("");
- rootLogger.setLevel(level);
-
- // Configure console handler
- for (var handler : rootLogger.getHandlers()) {
- if (handler instanceof ConsoleHandler) {
- handler.setLevel(level);
- }
- }
+ }
+
+ private static void configureLogging(Level level) {
+ // Get root logger
+ final var rootLogger = Logger.getLogger("");
+ rootLogger.setLevel(level);
+
+ // Configure console handler
+ for (var handler : rootLogger.getHandlers()) {
+ if (handler instanceof ConsoleHandler) {
+ handler.setLevel(level);
+ }
}
+ }
}
\ No newline at end of file
diff --git a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
index 5980d36..c971395 100644
--- a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
+++ b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
@@ -11,33 +11,29 @@
import jdk.sandbox.java.util.json.JsonArray;
import jdk.sandbox.java.util.json.JsonObject;
import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
import java.util.Set;
import java.util.Map;
-import java.util.logging.Logger;
-import java.util.logging.Level;
public class ApiTrackerTest {
- private static final Logger LOGGER = Logger.getLogger(ApiTrackerTest.class.getName());
-
- @BeforeAll
+
+ @BeforeAll
static void setupLogging() {
LoggingControl.setupCleanLogging();
}
-
+
@Nested
@DisplayName("Local Class Discovery")
class LocalDiscoveryTests {
-
+
@Test
@DisplayName("Should discover JSON API classes")
void testDiscoverLocalJsonClasses() {
final var classes = ApiTracker.discoverLocalJsonClasses();
-
+
assertThat(classes).isNotNull();
assertThat(classes).isNotEmpty();
-
+
// Should find core JSON interfaces
assertThat(classes.stream().map(Class::getName))
.contains(
@@ -49,28 +45,28 @@ void testDiscoverLocalJsonClasses() {
"jdk.sandbox.java.util.json.JsonBoolean",
"jdk.sandbox.java.util.json.JsonNull"
);
-
+
// Should NOT find internal implementation classes (public API only)
assertThat(classes.stream().anyMatch(c -> c.getName().startsWith("jdk.sandbox.internal.util.json")))
.as("Should not find internal implementation classes - public API only")
.isFalse();
-
+
// Should be sorted
final var names = classes.stream().map(Class::getName).toList();
final var sortedNames = names.stream().sorted().toList();
assertThat(names).isEqualTo(sortedNames);
}
}
-
+
@Nested
@DisplayName("Local API Extraction")
class LocalApiExtractionTests {
-
+
@Test
@DisplayName("Should extract API from JsonObject interface source")
void testExtractLocalApiJsonObject() {
final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.JsonObject");
-
+
assertThat(api).isNotNull();
// Check if extraction succeeded or failed
if (api.members().containsKey("error")) {
@@ -81,20 +77,20 @@ void testExtractLocalApiJsonObject() {
// If extraction succeeded, validate structure
assertThat(api.members()).containsKey("className");
assertThat(((JsonString) api.members().get("className")).value()).isEqualTo("JsonObject");
-
+
assertThat(api.members()).containsKey("packageName");
assertThat(((JsonString) api.members().get("packageName")).value()).isEqualTo("jdk.sandbox.java.util.json");
-
+
assertThat(api.members()).containsKey("isInterface");
assertThat(api.members().get("isInterface")).isEqualTo(JsonBoolean.of(true));
}
}
-
+
@Test
@DisplayName("Should extract API from JsonValue sealed interface source")
void testExtractLocalApiJsonValue() {
final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.JsonValue");
-
+
// Check if extraction succeeded or failed
if (api.members().containsKey("error")) {
// If file not found, that's expected for some source setups
@@ -104,39 +100,39 @@ void testExtractLocalApiJsonValue() {
// If extraction succeeded, validate structure
assertThat(api.members()).containsKey("isSealed");
assertThat(api.members().get("isSealed")).isEqualTo(JsonBoolean.of(true));
-
+
assertThat(api.members()).containsKey("permits");
final var permits = (JsonArray) api.members().get("permits");
// May be empty in source parsing if permits aren't explicitly listed
assertThat(permits).isNotNull();
}
}
-
+
@Test
@DisplayName("Should handle missing source file gracefully")
void testExtractLocalApiMissingFile() {
final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.NonExistentClass");
-
+
assertThat(api.members()).containsKey("error");
final var error = ((JsonString) api.members().get("error")).value();
assertThat(error).contains("LOCAL_FILE_NOT_FOUND");
}
}
-
+
@Nested
@DisplayName("Upstream Source Fetching")
class UpstreamFetchingTests {
-
+
@Test
@DisplayName("Should map local class names to upstream paths")
void testMapToUpstreamPath() {
assertThat(ApiTracker.mapToUpstreamPath("jdk.sandbox.java.util.json.JsonObject"))
.isEqualTo("java/util/json/JsonObject.java");
-
+
assertThat(ApiTracker.mapToUpstreamPath("jdk.sandbox.internal.util.json.JsonObjectImpl"))
.isEqualTo("jdk/internal/util/json/JsonObjectImpl.java");
}
-
+
@Test
@DisplayName("Should handle null parameter in fetchUpstreamSources")
void testFetchUpstreamSourcesNull() {
@@ -144,7 +140,7 @@ void testFetchUpstreamSourcesNull() {
.isInstanceOf(NullPointerException.class)
.hasMessage("localClasses must not be null");
}
-
+
@Test
@DisplayName("Should return empty map for empty input")
void testFetchUpstreamSourcesEmpty() {
@@ -152,25 +148,25 @@ void testFetchUpstreamSourcesEmpty() {
assertThat(result).isEmpty();
}
}
-
+
@Nested
@DisplayName("API Comparison")
class ApiComparisonTests {
-
+
@Test
@DisplayName("Should handle null parameters in compareApis")
void testCompareApisNull() {
final var dummyApi = JsonObject.of(Map.of("className", JsonString.of("Test")));
-
+
assertThatThrownBy(() -> ApiTracker.compareApis(null, dummyApi))
.isInstanceOf(NullPointerException.class)
.hasMessage("local must not be null");
-
+
assertThatThrownBy(() -> ApiTracker.compareApis(dummyApi, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("upstream must not be null");
}
-
+
@Test
@DisplayName("Should handle upstream errors in comparison")
void testCompareApisUpstreamError() {
@@ -179,24 +175,24 @@ void testCompareApisUpstreamError() {
"error", JsonString.of("NOT_FOUND: File not found"),
"className", JsonString.of("TestClass")
));
-
+
final var result = ApiTracker.compareApis(local, upstream);
-
+
assertThat(result.members()).containsKey("status");
assertThat(((JsonString) result.members().get("status")).value()).isEqualTo("UPSTREAM_ERROR");
assertThat(result.members()).containsKey("error");
}
}
-
+
@Nested
@DisplayName("Full Comparison Orchestration")
class FullComparisonTests {
-
+
@Test
@DisplayName("Should run full comparison and return report structure")
void testRunFullComparison() {
final var report = ApiTracker.runFullComparison();
-
+
assertThat(report).isNotNull();
assertThat(report.members()).containsKeys(
"timestamp",
@@ -206,7 +202,7 @@ void testRunFullComparison() {
"differences",
"durationMs"
);
-
+
final var summary = (JsonObject) report.members().get("summary");
assertThat(summary.members()).containsKeys(
"totalClasses",
@@ -214,26 +210,26 @@ void testRunFullComparison() {
"missingUpstream",
"differentApi"
);
-
+
// Total classes should be greater than 0
final var totalClasses = summary.members().get("totalClasses");
assertThat(totalClasses).isNotNull();
}
}
-
+
@Nested
@DisplayName("Type Name Normalization")
class TypeNameNormalizationTests {
-
+
@Test
@DisplayName("Should normalize type names correctly")
void testNormalizeTypeName() {
assertThat(ApiTracker.normalizeTypeName("jdk.sandbox.java.util.json.JsonValue"))
.isEqualTo("JsonValue");
-
+
assertThat(ApiTracker.normalizeTypeName("java.lang.String"))
.isEqualTo("String");
-
+
assertThat(ApiTracker.normalizeTypeName("String"))
.isEqualTo("String");
}
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java b/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java
index 626b65f..cdc6658 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java
+++ b/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java
@@ -7,63 +7,64 @@
* for thread-safe lazy initialization.
*/
class StableValue {
- private volatile T value;
- private final Object lock = new Object();
+ private volatile T value;
+ private final Object lock = new Object();
- private StableValue() {}
+ private StableValue() {
+ }
- public static StableValue of() {
- return new StableValue<>();
- }
+ public static StableValue of() {
+ return new StableValue<>();
+ }
public T orElse(T defaultValue) {
- T result = value;
- return result != null ? result : defaultValue;
- }
+ T result = value;
+ return result != null ? result : defaultValue;
+ }
- public T orElseSet(Supplier supplier) {
- T result = value;
+ public T orElseSet(Supplier supplier) {
+ T result = value;
+ if (result == null) {
+ synchronized (lock) {
+ result = value;
if (result == null) {
- synchronized (lock) {
- result = value;
- if (result == null) {
- value = result = supplier.get();
- }
- }
+ value = result = supplier.get();
}
- return result;
+ }
}
+ return result;
+ }
- public void setOrThrow(T newValue) {
- if (value != null) {
- throw new IllegalStateException("Value already set");
- }
- synchronized (lock) {
- if (value != null) {
- throw new IllegalStateException("Value already set");
- }
- value = newValue;
- }
+ public void setOrThrow(T newValue) {
+ if (value != null) {
+ throw new IllegalStateException("Value already set");
+ }
+ synchronized (lock) {
+ if (value != null) {
+ throw new IllegalStateException("Value already set");
+ }
+ value = newValue;
}
+ }
- public static Supplier supplier(Supplier supplier) {
- return new Supplier<>() {
- private volatile T cached;
- private final Object supplierLock = new Object();
+ public static Supplier supplier(Supplier supplier) {
+ return new Supplier<>() {
+ private volatile T cached;
+ private final Object supplierLock = new Object();
- @Override
- public T get() {
- T result = cached;
+ @Override
+ public T get() {
+ T result = cached;
+ if (result == null) {
+ synchronized (supplierLock) {
+ result = cached;
if (result == null) {
- synchronized (supplierLock) {
- result = cached;
- if (result == null) {
- cached = result = supplier.get();
- }
- }
+ cached = result = supplier.get();
}
- return result;
}
- };
- }
+ }
+ return result;
+ }
+ };
+ }
}
\ No newline at end of file
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java b/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java
index 299d2c0..860358f 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java
+++ b/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java
@@ -12,24 +12,7 @@ public class JsonParserTests {
@Test
void testParseComplexJson() {
- String json = """
- {
- "name": "John Doe",
- "age": 30,
- "isStudent": false,
- "courses": [
- {"title": "History", "credits": 3},
- {"title": "Math", "credits": 4}
- ],
- "address": {
- "street": "123 Main St",
- "city": "Anytown"
- }
- }
- """;
-
- JsonParser parser = new JsonParser(json.toCharArray());
- JsonObject jsonObject = (JsonObject) parser.parseRoot();
+ JsonObject jsonObject = complexJsonObject();
assertThat(((JsonString) jsonObject.members().get("name")).value()).isEqualTo("John Doe");
assertThat(((JsonNumber) jsonObject.members().get("age")).toNumber().longValue()).isEqualTo(30L);
@@ -38,7 +21,7 @@ void testParseComplexJson() {
JsonArray courses = (JsonArray) jsonObject.members().get("courses");
assertThat(courses.values()).hasSize(2);
- JsonObject course1 = (JsonObject) courses.values().get(0);
+ JsonObject course1 = (JsonObject) courses.values().getFirst();
assertThat(((JsonString) course1.members().get("title")).value()).isEqualTo("History");
assertThat(((JsonNumber) course1.members().get("credits")).toNumber().longValue()).isEqualTo(3L);
@@ -50,4 +33,25 @@ void testParseComplexJson() {
assertThat(((JsonString) address.members().get("street")).value()).isEqualTo("123 Main St");
assertThat(((JsonString) address.members().get("city")).value()).isEqualTo("Anytown");
}
+
+ private static JsonObject complexJsonObject() {
+ String json = """
+ {
+ "name": "John Doe",
+ "age": 30,
+ "isStudent": false,
+ "courses": [
+ {"title": "History", "credits": 3},
+ {"title": "Math", "credits": 4}
+ ],
+ "address": {
+ "street": "123 Main St",
+ "city": "Anytown"
+ }
+ }
+ """;
+
+ JsonParser parser = new JsonParser(json.toCharArray());
+ return (JsonObject) parser.parseRoot();
+ }
}
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java b/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java
index c9a5eb9..e7202d1 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java
+++ b/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java
@@ -1,7 +1,6 @@
package jdk.sandbox.internal.util.json;
-import jdk.sandbox.java.util.json.Json;
import jdk.sandbox.java.util.json.JsonArray;
import jdk.sandbox.java.util.json.JsonNumber;
import jdk.sandbox.java.util.json.JsonObject;
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/JsonTypedUntypedTests.java b/json-java21/src/test/java/jdk/sandbox/java/util/json/JsonTypedUntypedTests.java
index 6ec623b..a981ce9 100644
--- a/json-java21/src/test/java/jdk/sandbox/java/util/json/JsonTypedUntypedTests.java
+++ b/json-java21/src/test/java/jdk/sandbox/java/util/json/JsonTypedUntypedTests.java
@@ -12,223 +12,225 @@
public class JsonTypedUntypedTests {
- @Test
- void testFromUntypedWithSimpleTypes() {
- // Test string
- JsonValue jsonString = Json.fromUntyped("hello");
- assertThat(jsonString).isInstanceOf(JsonString.class);
- assertThat(((JsonString) jsonString).value()).isEqualTo("hello");
-
- // Test integer
- JsonValue jsonInt = Json.fromUntyped(42);
- assertThat(jsonInt).isInstanceOf(JsonNumber.class);
- assertThat(((JsonNumber) jsonInt).toNumber()).isEqualTo(42L);
-
- // Test long
- JsonValue jsonLong = Json.fromUntyped(42L);
- assertThat(jsonLong).isInstanceOf(JsonNumber.class);
- assertThat(((JsonNumber) jsonLong).toNumber()).isEqualTo(42L);
-
- // Test double
- JsonValue jsonDouble = Json.fromUntyped(3.14);
- assertThat(jsonDouble).isInstanceOf(JsonNumber.class);
- assertThat(((JsonNumber) jsonDouble).toNumber()).isEqualTo(3.14);
-
- // Test boolean
- JsonValue jsonBool = Json.fromUntyped(true);
- assertThat(jsonBool).isInstanceOf(JsonBoolean.class);
- assertThat(((JsonBoolean) jsonBool).value()).isTrue();
-
- // Test null
- JsonValue jsonNull = Json.fromUntyped(null);
- assertThat(jsonNull).isInstanceOf(JsonNull.class);
- }
-
- @Test
- void testFromUntypedWithBigNumbers() {
- // Test BigInteger
- BigInteger bigInt = new BigInteger("123456789012345678901234567890");
- JsonValue jsonBigInt = Json.fromUntyped(bigInt);
- assertThat(jsonBigInt).isInstanceOf(JsonNumber.class);
- assertThat(((JsonNumber) jsonBigInt).toNumber()).isEqualTo(bigInt);
-
- // Test BigDecimal
- BigDecimal bigDec = new BigDecimal("123456789012345678901234567890.123456789");
- JsonValue jsonBigDec = Json.fromUntyped(bigDec);
- assertThat(jsonBigDec).isInstanceOf(JsonNumber.class);
- assertThat(((JsonNumber) jsonBigDec).toNumber()).isEqualTo(bigDec);
- }
-
- @Test
- void testFromUntypedWithCollections() {
- // Test List
- List
org.apache.maven.plugins