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///
6859/// All functionality is exposed as static methods following functional programming principles
6960public sealed interface ApiTracker permits ApiTracker .Nothing {
7061
71- /// API extraction strategy interface
72- sealed interface ApiExtractor permits
73- ReflectionApiExtractor , SourceApiExtractor {
74- JsonObject extractApi (String identifier );
75- }
76-
77- /// Source location strategy interface
78- sealed interface SourceLocator permits
79- LocalSourceLocator , RemoteSourceLocator {
80- String locateSource (String className );
81- }
82-
83- /// Reflection-based API extractor
84- record ReflectionApiExtractor () implements ApiExtractor {
85- @ Override
86- public JsonObject extractApi (String className ) {
87- try {
88- final var clazz = Class .forName (className );
89- return extractLocalApiFromClass (clazz );
90- } catch (ClassNotFoundException e ) {
91- return JsonObject .of (Map .of (
92- "error" , JsonString .of ("CLASS_NOT_FOUND: " + e .getMessage ()),
93- "className" , JsonString .of (className )
94- ));
95- }
96- }
97- }
98-
99- /// Source-based API extractor
100- record SourceApiExtractor (SourceLocator sourceLocator ) implements ApiExtractor {
101- @ Override
102- public JsonObject extractApi (String className ) {
103- final var sourceCode = sourceLocator .locateSource (className );
104- return extractApiFromSource (sourceCode , className );
105- }
106- }
107-
108- /// Local source file locator
109- record LocalSourceLocator (String sourceRoot ) implements SourceLocator {
110- @ Override
111- public String locateSource (String className ) {
112- final var path = sourceRoot + "/" + className .replace ('.' , '/' ) + ".java" ;
113- try {
114- return java .nio .file .Files .readString (java .nio .file .Paths .get (path ));
115- } catch (Exception e ) {
116- return "FILE_NOT_FOUND: " + e .getMessage ();
117- }
118- }
119- }
120-
121- /// Remote source locator (GitHub)
122- record RemoteSourceLocator (String baseUrl ) implements SourceLocator {
123- @ Override
124- public String locateSource (String className ) {
125- final var upstreamPath = mapToUpstreamPath (className );
126- final var url = baseUrl + upstreamPath ;
127- return fetchFromUrl (url );
128- }
129- }
130-
131- /// Comparison orchestrator
132- record ApiComparator (
133- ApiExtractor localExtractor ,
134- ApiExtractor upstreamExtractor
135- ) {
136- JsonObject compare (String className ) {
137- final var localApi = localExtractor .extractApi (className );
138- final var upstreamApi = upstreamExtractor .extractApi (className );
139- return compareApis (localApi , upstreamApi );
140- }
141- }
62+ /// Local source root for source-based extraction
63+ static final String LOCAL_SOURCE_ROOT = "json-java21/src/main/java" ;
14264
14365 /// Empty enum to seal the interface - no instances allowed
14466 enum Nothing implements ApiTracker {}
@@ -361,143 +283,18 @@ static String mapToUpstreamPath(String className) {
361283 return path + ".java" ;
362284 }
363285
364- /// Extracts public API from a compiled class using reflection
365- /// @param clazz the class to extract API from
366- /// @return JSON representation of the class's public API
367- static JsonObject extractLocalApiFromClass (Class <?> clazz ) {
368- Objects .requireNonNull (clazz , "clazz must not be null" );
369- LOGGER .info ("Extracting local API for: " + clazz .getName ());
370-
371- final var apiMap = new LinkedHashMap <String , JsonValue >();
372-
373- // Basic class information
374- apiMap .put ("className" , JsonString .of (clazz .getSimpleName ()));
375- apiMap .put ("packageName" , JsonString .of (clazz .getPackage () != null ? clazz .getPackage ().getName () : "" ));
376- apiMap .put ("modifiers" , extractModifiers (clazz .getModifiers ()));
377-
378- // Type information
379- apiMap .put ("isInterface" , JsonBoolean .of (clazz .isInterface ()));
380- apiMap .put ("isEnum" , JsonBoolean .of (clazz .isEnum ()));
381- apiMap .put ("isRecord" , JsonBoolean .of (clazz .isRecord ()));
382- apiMap .put ("isSealed" , JsonBoolean .of (clazz .isSealed ()));
383-
384- // Inheritance
385- final var superTypes = new ArrayList <JsonValue >();
386- if (clazz .getSuperclass () != null && !Object .class .equals (clazz .getSuperclass ())) {
387- superTypes .add (JsonString .of (clazz .getSuperclass ().getSimpleName ()));
388- }
389- Arrays .stream (clazz .getInterfaces ())
390- .map (i -> JsonString .of (i .getSimpleName ()))
391- .forEach (superTypes ::add );
392- apiMap .put ("extends" , JsonArray .of (superTypes ));
393-
394- // Permitted subclasses (for sealed types)
395- if (clazz .isSealed ()) {
396- final var permits = Arrays .stream (clazz .getPermittedSubclasses ())
397- .map (c -> JsonString .of (c .getSimpleName ()))
398- .collect (Collectors .<JsonValue >toList ());
399- 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+ ));
400297 }
401-
402- // Methods
403- apiMap .put ("methods" , extractMethods (clazz ));
404-
405- // Fields
406- apiMap .put ("fields" , extractFields (clazz ));
407-
408- // Constructors
409- apiMap .put ("constructors" , extractConstructors (clazz ));
410-
411- return JsonObject .of (apiMap );
412- }
413-
414- /// Extracts modifiers as JSON array
415- static JsonArray extractModifiers (int modifiers ) {
416- final var modList = new ArrayList <JsonValue >();
417-
418- if (Modifier .isPublic (modifiers )) modList .add (JsonString .of ("public" ));
419- if (Modifier .isProtected (modifiers )) modList .add (JsonString .of ("protected" ));
420- if (Modifier .isPrivate (modifiers )) modList .add (JsonString .of ("private" ));
421- if (Modifier .isStatic (modifiers )) modList .add (JsonString .of ("static" ));
422- if (Modifier .isFinal (modifiers )) modList .add (JsonString .of ("final" ));
423- if (Modifier .isAbstract (modifiers )) modList .add (JsonString .of ("abstract" ));
424- if (Modifier .isNative (modifiers )) modList .add (JsonString .of ("native" ));
425- if (Modifier .isSynchronized (modifiers )) modList .add (JsonString .of ("synchronized" ));
426- if (Modifier .isTransient (modifiers )) modList .add (JsonString .of ("transient" ));
427- if (Modifier .isVolatile (modifiers )) modList .add (JsonString .of ("volatile" ));
428-
429- return JsonArray .of (modList );
430- }
431-
432- /// Extracts public methods
433- static JsonObject extractMethods (Class <?> clazz ) {
434- final var methodsMap = new LinkedHashMap <String , JsonValue >();
435-
436- Arrays .stream (clazz .getDeclaredMethods ())
437- .filter (m -> Modifier .isPublic (m .getModifiers ()))
438- .forEach (method -> {
439- final var methodInfo = new LinkedHashMap <String , JsonValue >();
440- methodInfo .put ("modifiers" , extractModifiers (method .getModifiers ()));
441- methodInfo .put ("returnType" , JsonString .of (method .getReturnType ().getSimpleName ()));
442- methodInfo .put ("genericReturnType" , JsonString .of (method .getGenericReturnType ().getTypeName ()));
443-
444- final var params = Arrays .stream (method .getParameters ())
445- .map (p -> JsonString .of (p .getType ().getSimpleName () + " " + p .getName ()))
446- .collect (Collectors .<JsonValue >toList ());
447- methodInfo .put ("parameters" , JsonArray .of (params ));
448-
449- final var exceptions = Arrays .stream (method .getExceptionTypes ())
450- .map (e -> JsonString .of (e .getSimpleName ()))
451- .collect (Collectors .<JsonValue >toList ());
452- methodInfo .put ("throws" , JsonArray .of (exceptions ));
453-
454- methodsMap .put (method .getName (), JsonObject .of (methodInfo ));
455- });
456-
457- return JsonObject .of (methodsMap );
458- }
459-
460- /// Extracts public fields
461- static JsonObject extractFields (Class <?> clazz ) {
462- final var fieldsMap = new LinkedHashMap <String , JsonValue >();
463-
464- Arrays .stream (clazz .getDeclaredFields ())
465- .filter (f -> Modifier .isPublic (f .getModifiers ()))
466- .forEach (field -> {
467- final var fieldInfo = new LinkedHashMap <String , JsonValue >();
468- fieldInfo .put ("modifiers" , extractModifiers (field .getModifiers ()));
469- fieldInfo .put ("type" , JsonString .of (field .getType ().getSimpleName ()));
470- fieldInfo .put ("genericType" , JsonString .of (field .getGenericType ().getTypeName ()));
471-
472- fieldsMap .put (field .getName (), JsonObject .of (fieldInfo ));
473- });
474-
475- return JsonObject .of (fieldsMap );
476- }
477-
478- /// Extracts public constructors
479- static JsonArray extractConstructors (Class <?> clazz ) {
480- final var constructors = Arrays .stream (clazz .getDeclaredConstructors ())
481- .filter (c -> Modifier .isPublic (c .getModifiers ()))
482- .map (constructor -> {
483- final var ctorInfo = new LinkedHashMap <String , JsonValue >();
484- ctorInfo .put ("modifiers" , extractModifiers (constructor .getModifiers ()));
485-
486- final var params = Arrays .stream (constructor .getParameters ())
487- .map (p -> JsonString .of (p .getType ().getSimpleName () + " " + p .getName ()))
488- .collect (Collectors .<JsonValue >toList ());
489- ctorInfo .put ("parameters" , JsonArray .of (params ));
490-
491- final var exceptions = Arrays .stream (constructor .getExceptionTypes ())
492- .map (e -> JsonString .of (e .getSimpleName ()))
493- .collect (Collectors .<JsonValue >toList ());
494- ctorInfo .put ("throws" , JsonArray .of (exceptions ));
495-
496- return JsonObject .of (ctorInfo );
497- })
498- .collect (Collectors .<JsonValue >toList ());
499-
500- return JsonArray .of (constructors );
501298 }
502299
503300 /// Extracts public API from source code using compiler parsing
@@ -1087,33 +884,9 @@ static String normalizeTypeName(String typeName) {
1087884 return normalized ;
1088885 }
1089886
1090- /// Runs a full comparison of local vs upstream APIs using reflection vs source parsing
1091- /// @return complete comparison report as JSON
1092- static JsonObject runFullComparison () {
1093- return runComparisonWithExtractors (
1094- new ReflectionApiExtractor (),
1095- new SourceApiExtractor (new RemoteSourceLocator (GITHUB_BASE_URL ))
1096- );
1097- }
1098-
1099- /// Runs a source-to-source comparison for apples-to-apples comparison
1100- /// @param localSourceRoot path to local source files
887+ /// Runs source-to-source comparison for fair parameter name comparison
1101888 /// @return complete comparison report as JSON
1102- static JsonObject runSourceToSourceComparison (String localSourceRoot ) {
1103- return runComparisonWithExtractors (
1104- new SourceApiExtractor (new LocalSourceLocator (localSourceRoot )),
1105- new SourceApiExtractor (new RemoteSourceLocator (GITHUB_BASE_URL ))
1106- );
1107- }
1108-
1109- /// Runs comparison with specified extractors
1110- /// @param localExtractor extractor for local API
1111- /// @param upstreamExtractor extractor for upstream API
1112- /// @return complete comparison report as JSON
1113- static JsonObject runComparisonWithExtractors (
1114- ApiExtractor localExtractor ,
1115- ApiExtractor upstreamExtractor
1116- ) {
889+ static JsonObject runFullComparison () {
1117890 LOGGER .info ("Starting full API comparison" );
1118891 final var startTime = Instant .now ();
1119892
@@ -1132,10 +905,12 @@ static JsonObject runComparisonWithExtractors(
1132905 var missingUpstream = 0 ;
1133906 var differentApi = 0 ;
1134907
1135- final var comparator = new ApiComparator (localExtractor , upstreamExtractor );
1136-
1137908 for (final var clazz : localClasses ) {
1138- final var diff = comparator .compare (clazz .getName ());
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 );
913+ final var diff = compareApis (localApi , upstreamApi );
1139914 differences .add (diff );
1140915
1141916 // Count statistics
@@ -1158,11 +933,26 @@ static JsonObject runComparisonWithExtractors(
1158933 reportMap .put ("summary" , summary );
1159934 reportMap .put ("differences" , JsonArray .of (differences ));
1160935
936+
1161937 final var duration = Duration .between (startTime , Instant .now ());
1162938 reportMap .put ("durationMs" , JsonNumber .of (duration .toMillis ()));
1163939
1164940 LOGGER .info ("Comparison completed in " + duration .toMillis () + "ms" );
1165941
1166942 return JsonObject .of (reportMap );
1167943 }
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+ }
1168958}
0 commit comments