88import jdk .sandbox .java .util .json .JsonBoolean ;
99
1010import java .io .IOException ;
11- import java .lang .reflect .Constructor ;
12- import java .lang .reflect .Field ;
13- import java .lang .reflect .Method ;
14- import java .lang .reflect .Modifier ;
15- import java .lang .reflect .ParameterizedType ;
16- import java .lang .reflect .Type ;
1711import java .net .URI ;
1812import java .net .http .HttpClient ;
1913import java .net .http .HttpRequest ;
2216import java .time .Duration ;
2317import java .time .Instant ;
2418import java .util .ArrayList ;
25- import java .util .Arrays ;
19+ import java .util .ArrayList ;
2620import java .util .Collections ;
2721import java .util .LinkedHashMap ;
2822import java .util .List ;
3428import java .util .logging .Level ;
3529import java .util .logging .Logger ;
3630import java .util .stream .Collectors ;
37- import java .util .stream .Stream ;
3831
3932import javax .tools .DiagnosticCollector ;
4033import javax .tools .JavaCompiler ;
4437import javax .tools .ToolProvider ;
4538
4639import com .sun .source .tree .ClassTree ;
47- import com .sun .source .tree .CompilationUnitTree ;
4840import com .sun .source .tree .MethodTree ;
4941import com .sun .source .tree .ModifiersTree ;
5042import com .sun .source .tree .Tree ;
5143import com .sun .source .tree .VariableTree ;
5244import com .sun .source .util .JavacTask ;
5345import com .sun .source .util .TreePathScanner ;
54- import com .sun .source .util .Trees ;
5546
5647/// API Tracker module for comparing local and upstream JSON APIs
5748///
6152/// - Compare public APIs using compiler parsing
6253/// - Generate structured diff reports
6354///
55+ /// Modular design supports different extraction strategies:
56+ /// - Binary reflection for quick class introspection
57+ /// - Source parsing for accurate parameter names and signatures
58+ ///
6459/// All functionality is exposed as static methods following functional programming principles
6560public sealed interface ApiTracker permits ApiTracker .Nothing {
6661
62+ /// Local source root for source-based extraction
63+ static final String LOCAL_SOURCE_ROOT = "json-java21/src/main/java" ;
64+
6765 /// Empty enum to seal the interface - no instances allowed
6866 enum Nothing implements ApiTracker {}
6967
@@ -76,6 +74,33 @@ enum Nothing implements ApiTracker {}
7674 // GitHub base URL for upstream sources
7775 static final String GITHUB_BASE_URL = "https://raw.githubusercontent.com/openjdk/jdk-sandbox/refs/heads/json/src/java.base/share/classes/" ;
7876
77+ /// Fetches content from a URL
78+ static String fetchFromUrl (String url ) {
79+ final var httpClient = HttpClient .newBuilder ()
80+ .connectTimeout (Duration .ofSeconds (10 ))
81+ .build ();
82+
83+ try {
84+ final var request = HttpRequest .newBuilder ()
85+ .uri (URI .create (url ))
86+ .timeout (Duration .ofSeconds (30 ))
87+ .GET ()
88+ .build ();
89+
90+ final var response = httpClient .send (request , HttpResponse .BodyHandlers .ofString ());
91+
92+ if (response .statusCode () == 200 ) {
93+ return response .body ();
94+ } else if (response .statusCode () == 404 ) {
95+ return "NOT_FOUND: Upstream file not found (possibly deleted or renamed)" ;
96+ } else {
97+ return "HTTP_ERROR: Status " + response .statusCode ();
98+ }
99+ } catch (Exception e ) {
100+ return "FETCH_ERROR: " + e .getMessage ();
101+ }
102+ }
103+
79104 /// Discovers all classes in the local JSON API packages
80105 /// @return sorted set of classes from jdk.sandbox.java.util.json and jdk.sandbox.internal.util.json
81106 static Set <Class <?>> discoverLocalJsonClasses () {
@@ -258,150 +283,25 @@ static String mapToUpstreamPath(String className) {
258283 return path + ".java" ;
259284 }
260285
261- /// Extracts public API from a compiled class using reflection
262- /// @param clazz the class to extract API from
263- /// @return JSON representation of the class's public API
264- static JsonObject extractLocalApi (Class <?> clazz ) {
265- Objects .requireNonNull (clazz , "clazz must not be null" );
266- LOGGER .info ("Extracting local API for: " + clazz .getName ());
267-
268- final var apiMap = new LinkedHashMap <String , JsonValue >();
269-
270- // Basic class information
271- apiMap .put ("className" , JsonString .of (clazz .getSimpleName ()));
272- apiMap .put ("packageName" , JsonString .of (clazz .getPackage () != null ? clazz .getPackage ().getName () : "" ));
273- apiMap .put ("modifiers" , extractModifiers (clazz .getModifiers ()));
274-
275- // Type information
276- apiMap .put ("isInterface" , JsonBoolean .of (clazz .isInterface ()));
277- apiMap .put ("isEnum" , JsonBoolean .of (clazz .isEnum ()));
278- apiMap .put ("isRecord" , JsonBoolean .of (clazz .isRecord ()));
279- apiMap .put ("isSealed" , JsonBoolean .of (clazz .isSealed ()));
280-
281- // Inheritance
282- final var superTypes = new ArrayList <JsonValue >();
283- if (clazz .getSuperclass () != null && !Object .class .equals (clazz .getSuperclass ())) {
284- superTypes .add (JsonString .of (clazz .getSuperclass ().getSimpleName ()));
285- }
286- Arrays .stream (clazz .getInterfaces ())
287- .map (i -> JsonString .of (i .getSimpleName ()))
288- .forEach (superTypes ::add );
289- apiMap .put ("extends" , JsonArray .of (superTypes ));
290-
291- // Permitted subclasses (for sealed types)
292- if (clazz .isSealed ()) {
293- final var permits = Arrays .stream (clazz .getPermittedSubclasses ())
294- .map (c -> JsonString .of (c .getSimpleName ()))
295- .collect (Collectors .<JsonValue >toList ());
296- apiMap .put ("permits" , JsonArray .of (permits ));
286+ /// Extracts local API from source file
287+ static JsonObject extractLocalApiFromSource (String className ) {
288+ final var path = LOCAL_SOURCE_ROOT + "/" + className .replace ('.' , '/' ) + ".java" ;
289+ try {
290+ final var sourceCode = java .nio .file .Files .readString (java .nio .file .Paths .get (path ));
291+ return extractApiFromSource (sourceCode , className );
292+ } catch (Exception e ) {
293+ return JsonObject .of (Map .of (
294+ "error" , JsonString .of ("LOCAL_FILE_NOT_FOUND: " + e .getMessage ()),
295+ "className" , JsonString .of (className )
296+ ));
297297 }
298-
299- // Methods
300- apiMap .put ("methods" , extractMethods (clazz ));
301-
302- // Fields
303- apiMap .put ("fields" , extractFields (clazz ));
304-
305- // Constructors
306- apiMap .put ("constructors" , extractConstructors (clazz ));
307-
308- return JsonObject .of (apiMap );
309- }
310-
311- /// Extracts modifiers as JSON array
312- static JsonArray extractModifiers (int modifiers ) {
313- final var modList = new ArrayList <JsonValue >();
314-
315- if (Modifier .isPublic (modifiers )) modList .add (JsonString .of ("public" ));
316- if (Modifier .isProtected (modifiers )) modList .add (JsonString .of ("protected" ));
317- if (Modifier .isPrivate (modifiers )) modList .add (JsonString .of ("private" ));
318- if (Modifier .isStatic (modifiers )) modList .add (JsonString .of ("static" ));
319- if (Modifier .isFinal (modifiers )) modList .add (JsonString .of ("final" ));
320- if (Modifier .isAbstract (modifiers )) modList .add (JsonString .of ("abstract" ));
321- if (Modifier .isNative (modifiers )) modList .add (JsonString .of ("native" ));
322- if (Modifier .isSynchronized (modifiers )) modList .add (JsonString .of ("synchronized" ));
323- if (Modifier .isTransient (modifiers )) modList .add (JsonString .of ("transient" ));
324- if (Modifier .isVolatile (modifiers )) modList .add (JsonString .of ("volatile" ));
325-
326- return JsonArray .of (modList );
327- }
328-
329- /// Extracts public methods
330- static JsonObject extractMethods (Class <?> clazz ) {
331- final var methodsMap = new LinkedHashMap <String , JsonValue >();
332-
333- Arrays .stream (clazz .getDeclaredMethods ())
334- .filter (m -> Modifier .isPublic (m .getModifiers ()))
335- .forEach (method -> {
336- final var methodInfo = new LinkedHashMap <String , JsonValue >();
337- methodInfo .put ("modifiers" , extractModifiers (method .getModifiers ()));
338- methodInfo .put ("returnType" , JsonString .of (method .getReturnType ().getSimpleName ()));
339- methodInfo .put ("genericReturnType" , JsonString .of (method .getGenericReturnType ().getTypeName ()));
340-
341- final var params = Arrays .stream (method .getParameters ())
342- .map (p -> JsonString .of (p .getType ().getSimpleName () + " " + p .getName ()))
343- .collect (Collectors .<JsonValue >toList ());
344- methodInfo .put ("parameters" , JsonArray .of (params ));
345-
346- final var exceptions = Arrays .stream (method .getExceptionTypes ())
347- .map (e -> JsonString .of (e .getSimpleName ()))
348- .collect (Collectors .<JsonValue >toList ());
349- methodInfo .put ("throws" , JsonArray .of (exceptions ));
350-
351- methodsMap .put (method .getName (), JsonObject .of (methodInfo ));
352- });
353-
354- return JsonObject .of (methodsMap );
355- }
356-
357- /// Extracts public fields
358- static JsonObject extractFields (Class <?> clazz ) {
359- final var fieldsMap = new LinkedHashMap <String , JsonValue >();
360-
361- Arrays .stream (clazz .getDeclaredFields ())
362- .filter (f -> Modifier .isPublic (f .getModifiers ()))
363- .forEach (field -> {
364- final var fieldInfo = new LinkedHashMap <String , JsonValue >();
365- fieldInfo .put ("modifiers" , extractModifiers (field .getModifiers ()));
366- fieldInfo .put ("type" , JsonString .of (field .getType ().getSimpleName ()));
367- fieldInfo .put ("genericType" , JsonString .of (field .getGenericType ().getTypeName ()));
368-
369- fieldsMap .put (field .getName (), JsonObject .of (fieldInfo ));
370- });
371-
372- return JsonObject .of (fieldsMap );
373298 }
374299
375- /// Extracts public constructors
376- static JsonArray extractConstructors (Class <?> clazz ) {
377- final var constructors = Arrays .stream (clazz .getDeclaredConstructors ())
378- .filter (c -> Modifier .isPublic (c .getModifiers ()))
379- .map (constructor -> {
380- final var ctorInfo = new LinkedHashMap <String , JsonValue >();
381- ctorInfo .put ("modifiers" , extractModifiers (constructor .getModifiers ()));
382-
383- final var params = Arrays .stream (constructor .getParameters ())
384- .map (p -> JsonString .of (p .getType ().getSimpleName () + " " + p .getName ()))
385- .collect (Collectors .<JsonValue >toList ());
386- ctorInfo .put ("parameters" , JsonArray .of (params ));
387-
388- final var exceptions = Arrays .stream (constructor .getExceptionTypes ())
389- .map (e -> JsonString .of (e .getSimpleName ()))
390- .collect (Collectors .<JsonValue >toList ());
391- ctorInfo .put ("throws" , JsonArray .of (exceptions ));
392-
393- return JsonObject .of (ctorInfo );
394- })
395- .collect (Collectors .<JsonValue >toList ());
396-
397- return JsonArray .of (constructors );
398- }
399-
400- /// Extracts public API from upstream source code using compiler parsing
300+ /// Extracts public API from source code using compiler parsing
401301 /// @param sourceCode the source code to parse
402302 /// @param className the expected class name
403303 /// @return JSON representation of the parsed API
404- static JsonObject extractUpstreamApi (String sourceCode , String className ) {
304+ static JsonObject extractApiFromSource (String sourceCode , String className ) {
405305 Objects .requireNonNull (sourceCode , "sourceCode must not be null" );
406306 Objects .requireNonNull (className , "className must not be null" );
407307
@@ -701,7 +601,11 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
701601 Objects .requireNonNull (upstream , "upstream must not be null" );
702602
703603 final var diffMap = new LinkedHashMap <String , JsonValue >();
704- final var className = ((JsonString ) local .members ().get ("className" )).value ();
604+
605+ // Extract class name safely
606+ final var localClassName = local .members ().get ("className" );
607+ final var className = localClassName instanceof JsonString js ?
608+ js .value () : "Unknown" ;
705609
706610 diffMap .put ("className" , JsonString .of (className ));
707611
@@ -980,7 +884,7 @@ static String normalizeTypeName(String typeName) {
980884 return normalized ;
981885 }
982886
983- /// Runs a full comparison of local vs upstream APIs
887+ /// Runs source-to-source comparison for fair parameter name comparison
984888 /// @return complete comparison report as JSON
985889 static JsonObject runFullComparison () {
986890 LOGGER .info ("Starting full API comparison" );
@@ -995,20 +899,17 @@ static JsonObject runFullComparison() {
995899 final var localClasses = discoverLocalJsonClasses ();
996900 LOGGER .info ("Found " + localClasses .size () + " local classes" );
997901
998- // Fetch upstream sources
999- final var upstreamSources = fetchUpstreamSources (localClasses );
1000-
1001902 // Extract and compare APIs
1002903 final var differences = new ArrayList <JsonValue >();
1003904 var matchingCount = 0 ;
1004905 var missingUpstream = 0 ;
1005906 var differentApi = 0 ;
1006907
1007908 for (final var clazz : localClasses ) {
1008- final var localApi = extractLocalApi ( clazz );
1009- final var upstreamSource = upstreamSources . get ( clazz . getName () );
1010- final var upstreamApi = extractUpstreamApi ( upstreamSource , clazz . getName () );
1011-
909+ final var className = clazz . getName ( );
910+ final var localApi = extractLocalApiFromSource ( className );
911+ final var upstreamSource = fetchUpstreamSource ( className );
912+ final var upstreamApi = extractApiFromSource ( upstreamSource , className );
1012913 final var diff = compareApis (localApi , upstreamApi );
1013914 differences .add (diff );
1014915
@@ -1032,11 +933,26 @@ static JsonObject runFullComparison() {
1032933 reportMap .put ("summary" , summary );
1033934 reportMap .put ("differences" , JsonArray .of (differences ));
1034935
936+
1035937 final var duration = Duration .between (startTime , Instant .now ());
1036938 reportMap .put ("durationMs" , JsonNumber .of (duration .toMillis ()));
1037939
1038940 LOGGER .info ("Comparison completed in " + duration .toMillis () + "ms" );
1039941
1040942 return JsonObject .of (reportMap );
1041943 }
944+
945+ /// Fetches single upstream source file
946+ static String fetchUpstreamSource (String className ) {
947+ final var cached = FETCH_CACHE .get (className );
948+ if (cached != null ) {
949+ return cached ;
950+ }
951+
952+ final var upstreamPath = mapToUpstreamPath (className );
953+ final var url = GITHUB_BASE_URL + upstreamPath ;
954+ final var source = fetchFromUrl (url );
955+ FETCH_CACHE .put (className , source );
956+ return source ;
957+ }
1042958}
0 commit comments