Skip to content

Commit da02f98

Browse files
Simon Masseyclaude
andauthored
feat: Add API tracker to compare local and upstream JSON APIs (#8)
* refactor: Convert to multi-module Maven project * build: Enforce stricter compiler settings and fix warnings * feat: Add API tracker to compare local and upstream JSON APIs - Implement API tracker module with reflection for local classes - Add compiler parsing for upstream source analysis - Create comparison logic to identify API differences - Add GitHub Action for daily API tracking (cron: 2 AM UTC) - Include command-line runner for manual testing The tracker discovers local classes, fetches upstream sources from GitHub, extracts public APIs using both reflection and compiler parsing, then generates a detailed comparison report showing any differences. Closes #7 * fix: Address PR review comments - Remove duplicate ApiTrackerMain class (keep only ApiTrackerRunner) - Replace hardcoded class list with dynamic classpath scanning - Change logging from FINE to INFO for key operations - Remove hardcoded version references The API tracker now dynamically discovers classes in the JSON API packages by scanning both directories and JAR files on the classpath. This makes it more robust and eliminates the need to manually update class lists. * fix: Address PR review comments - Update both GitHub workflows to use Java 24 instead of matrix build - Add comprehensive INFO logging for HTTP operations and class discovery - Remove scaffolding test and fix compilation warnings - Maintain Java 21 compatibility for main library while using Java 24 for tooling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * fix: Focus API tracker on public API only, eliminate false positives - Remove internal package scanning (jdk.sandbox.internal.util.json) - Track only public API classes (jdk.sandbox.java.util.json) - Eliminate false positive 404 errors for removed internal classes like StableValue - Reduce tracked classes from 19 to 10 (public API only) - Improve performance and focus on meaningful API drift detection - Update tests to verify no internal classes are tracked Results: 0 missing upstream, 10 public API classes, faster execution 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 38ea84e commit da02f98

38 files changed

+1984
-80
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Daily API Tracker
2+
3+
on:
4+
schedule:
5+
# Run daily at 2 AM UTC
6+
- cron: '0 2 * * *'
7+
workflow_dispatch: # Allow manual trigger for testing
8+
9+
jobs:
10+
track-api:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Set up JDK 24
18+
uses: actions/setup-java@v4
19+
with:
20+
java-version: '24'
21+
distribution: 'temurin'
22+
23+
- name: Cache Maven dependencies
24+
uses: actions/cache@v4
25+
with:
26+
path: ~/.m2
27+
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
28+
restore-keys: ${{ runner.os }}-m2
29+
30+
- name: Build project
31+
run: mvn clean compile -DskipTests
32+
33+
- name: Run API Tracker
34+
run: |
35+
mvn exec:java \
36+
-pl json-java21-api-tracker \
37+
-Dexec.mainClass="io.github.simbo1905.tracker.ApiTrackerRunner" \
38+
-Dexec.args="INFO" \
39+
-Djava.util.logging.ConsoleHandler.level=INFO
40+
41+
- name: Create issue if differences found
42+
if: failure()
43+
uses: actions/github-script@v7
44+
with:
45+
script: |
46+
const title = 'API differences detected between local and upstream';
47+
const body = `The daily API tracker found differences between our local implementation and upstream.
48+
49+
Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
50+
51+
Date: ${new Date().toISOString().split('T')[0]}`;
52+
53+
github.rest.issues.create({
54+
owner: context.repo.owner,
55+
repo: context.repo.repo,
56+
title: title,
57+
body: body,
58+
labels: ['api-tracking', 'upstream-sync']
59+
});

.github/workflows/maven.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,15 @@ on:
1010
jobs:
1111
build:
1212
runs-on: ubuntu-latest
13-
strategy:
14-
matrix:
15-
java: [ '21', '22', '23', '24' ]
16-
name: Build with JDK ${{ matrix.java }}
13+
name: Build with JDK 24
1714

1815
steps:
1916
- uses: actions/checkout@v4
2017

21-
- name: Set up JDK ${{ matrix.java }}
18+
- name: Set up JDK 24
2219
uses: actions/setup-java@v4
2320
with:
24-
java-version: ${{ matrix.java }}
21+
java-version: '24'
2522
distribution: 'oracle'
2623

2724
- name: Cache Maven dependencies

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ target/
33
.idea/
44

55
.claude/
6+
.aider*
7+
CLAUDE.md

CODING_STYLE_LLM.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Java DOP Coding Standards ####################
2+
3+
This file is a Gen AI summary of CODING_STYLE.md to use less tokens of context window. Read the original file for full details.
4+
5+
IMPORTANT: We do TDD so all code must include targeted unit tests.
6+
IMPORTANT: Never disable tests written for logic that we are yet to write we do Red-Green-Refactor coding.
7+
8+
## Core Principles
9+
10+
* Use Records for all data structures. Use sealed interfaces for protocols.
11+
* Prefer static methods with Records as parameters
12+
* Default to package-private scope
13+
* Package-by-feature, not package-by-layer
14+
* Create fewer, cohesive, wide packages (functionality modules or records as protocols)
15+
* Use public only when cross-package access is required
16+
* Use JEP 467 Markdown documentation examples: `/// good markdown` not legacy `/** bad html */`
17+
* Apply Data-Oriented Programming principles and avoid OOP
18+
* Use Stream operations instead of traditional loops. Never use `for(;;)` with mutable loop variables use
19+
`Arrays.setAll`
20+
* Prefer exhaustive destructuring switch expressions over if-else statements
21+
* Use destructuring switch expressions that operate on Records and sealed interfaces
22+
* Use anonymous variables in record destructuring and switch expressions
23+
* Use `final var` for local variables, parameters, and destructured fields
24+
* Apply JEP 371 "Local Classes and Interfaces" for cohesive files with narrow APIs
25+
26+
## Data-Oriented Programming
27+
28+
* Separate data (immutable Records) from behavior (never utility classes always static methods)
29+
* Use immutable generic data structures (maps, lists, sets) and take defense copies in constructors
30+
* Write pure functions that don't modify state
31+
* Leverage Java 21+ features:
32+
* Records for immutable data
33+
* Pattern matching for structural decomposition
34+
* Sealed classes for exhaustive switches
35+
* Virtual threads for concurrent processing
36+
37+
## Package Structure
38+
39+
* Use default (package-private) access as the standard. Do not use 'private' or 'public' by default.
40+
* Limit public to genuine cross-package APIs
41+
* Prefer package-private static methods. Do not use 'private' or 'public' by default.
42+
* Limit private to security-related code
43+
* Avoid anti-patterns: boilerplate OOP, excessive layering, dependency injection overuse
44+
45+
## Constants and Magic Numbers
46+
47+
* **NEVER use magic numbers** - always use enum constants
48+
* **NEVER write large if-else-if statements over known types** - will not be exhaustive and creates bugs when new types are added. Use exhaustive switch statements over bounded sets such as enum values or sealed interface permits
49+
50+
## Functional Style
51+
52+
* Combine Records + static methods for functional programming
53+
* Emphasize immutability and explicit state transformations
54+
* Reduce package count to improve testability
55+
* Implement Algebraic Data Types pattern with Function Modules
56+
* Modern Stream Programming
57+
* Use Stream API instead of traditional loops
58+
* Write declarative rather than imperative code
59+
* Chain operations without intermediate variables
60+
* Support immutability throughout processing
61+
* Example: `IntStream.range(0, 100).filter(i -> i % 2 == 0).sum()` instead of counting loops
62+
* Always use final variables in functional style.
63+
* Prefer `final var` with self documenting names over `int i` or `String s` but its not possible to do that on a `final` variable that is not yet initialized so its a weak preference not a strong one.
64+
* Avoid just adding new functionality to the top of a method to make an early return. It is fine to have a simple guard statement. Yet general you should pattern match over the input to do different things with the same method. Adding special case logic is a code smell that should be avoided.
65+
66+
## Documentation using JEP 467 Markdown documentation
67+
68+
IMPORTANT: You must not write JavaDoc comments that start with `/**` and end with `*/`
69+
IMPORTANT: You must "JEP 467: Markdown Documentation Comments" that start all lines with `///`
70+
71+
Here is an example of the correct format for documentation comments:
72+
73+
```java
74+
/// Returns a hash code value for the object. This method is
75+
/// supported for the benefit of hash tables such as those provided by
76+
/// [java.util.HashMap].
77+
///
78+
/// The general contract of `hashCode` is:
79+
///
80+
/// - Whenever it is invoked on the same object more than once during
81+
/// an execution of a Java application, the `hashCode` method
82+
/// - If two objects are equal according to the
83+
/// [equals][#equals(Object)] method, then calling the
84+
/// - It is _not_ required that if two objects are unequal
85+
/// according to the [equals][#equals(Object)] method, then
86+
///
87+
/// @return a hash code value for this object.
88+
/// @see java.lang.Object#equals(java.lang.Object)
89+
```
90+
91+
## Logging
92+
93+
- Use Java's built-in logging: `java.util.logging.Logger`
94+
- Log levels: Use appropriate levels (FINE, FINER, INFO, WARNING, SEVERE)
95+
- **FINE**: Production-level debugging, default for most debug output
96+
- **FINER**: Verbose debugging, detailed internal flow, class resolution details
97+
- **INFO**: Important runtime information
98+
- LOGGER is a static field: `static final Logger LOGGER = Logger.getLogger(ClassName.class.getName());` where use the primary interface or the package as the logger name with the logger package-private and shared across the classes when the package is small enough.
99+
- Use lambda logging for performance: `LOGGER.fine(() -> "message " + variable);`
100+
101+
# Compile, Test, Debug Loop
102+
103+
- **Check Compiles**: Focusing on the correct mvn module run without verbose logging and do not grep the output to see compile errors:
104+
```bash
105+
./mvn-test-no-boilerplate.sh -pl json-java21-api-tracker -Djava.util.logging.ConsoleHandler.level=SEVERE
106+
```
107+
- **Debug with Verbose Logs**: Use `-Dtest=` to focus on just one or two test methods, or one class, using more logging to debug the code:
108+
```bash
109+
./mvn-test-no-boilerplate.sh -pl json-java21-api-tracker -Dtest=XXX -Djava.util.logging.ConsoleHandler.level=FINER
110+
```
111+
- **No Grep Filtering**: Use logging levels to filter output, do not grep the output for compile errors, just run less test methods with the correct logging to reduce the output to a manageable size. Filtering hides problems and needs more test excution to find the same problems which wastes time.
112+
113+
## Modern Java Singleton Pattern: Sealed Interfaces
114+
115+
**Singleton Object Anti-Pattern**: Traditional singleton classes with private constructors and static instances are legacy should be avoided. With a functional style we can create a "package-private companion module" of small package-private methods with `sealed interfacee GoodSingletonModule permits Nothing { enum Nothing extends GoodSingletonModule{}; /* static functional methods here */ }`.
116+
117+
### Assertions and Input Validation
118+
119+
1. On the public API entry points use `Objects.assertNonNull()` to ensure that the inputs are legal.
120+
2. After that on internal method that should be passed only valid data use `assert` to ensure that the data is valid.
121+
- e.g. use `assert x==y: "unexpected x="+x+" y="+y;` as `mvn` base should be run with `-ea` to enable assertions.
122+
3. Often there is an `orElseThrow()` which can be used so the only reason to use `assert` is to add more logging to the error message.
123+
4. Consider using the validations of `Object` and `Arrays` and the like to ensure that the data is valid.
124+
- e.g. `Objects.requireNonNull(type, "type must not be null")` or `Arrays.checkIndex(index, array.length)`.
125+
126+
## JEP References
127+
128+
[JEP 467](https://openjdk.org/jeps/467): Markdown Documentation in JavaDoc
129+
[JEP 371](https://openjdk.org/jeps/371): Local Classes and Interfaces
130+
[JEP 395](https://openjdk.org/jeps/395): Records
131+
[JEP 409](https://openjdk.org/jeps/409): Sealed Classes
132+
[JEP 440](https://openjdk.org/jeps/440): Record Patterns
133+
[JEP 427](https://openjdk.org/jeps/427): Pattern Matching for Switch

json-java21-api-tracker/pom.xml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.github.simbo1905.json</groupId>
9+
<artifactId>json-java21-parent</artifactId>
10+
<version>0.1-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>json-java21-api-tracker</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>API Tracker</name>
17+
18+
<properties>
19+
<maven.compiler.release>24</maven.compiler.release>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>io.github.simbo1905.json</groupId>
25+
<artifactId>json-java21</artifactId>
26+
<version>${project.version}</version>
27+
</dependency>
28+
<!-- JUnit 5 for testing -->
29+
<dependency>
30+
<groupId>org.junit.jupiter</groupId>
31+
<artifactId>junit-jupiter-api</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.junit.jupiter</groupId>
36+
<artifactId>junit-jupiter-engine</artifactId>
37+
<scope>test</scope>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.assertj</groupId>
41+
<artifactId>assertj-core</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
</dependencies>
45+
46+
<profiles>
47+
<profile>
48+
<id>relaxed</id>
49+
<build>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-compiler-plugin</artifactId>
54+
<configuration>
55+
<compilerArgs>
56+
<arg>-Xlint:all</arg>
57+
</compilerArgs>
58+
</configuration>
59+
</plugin>
60+
</plugins>
61+
</build>
62+
</profile>
63+
</profiles>
64+
65+
<build>
66+
<plugins>
67+
<plugin>
68+
<groupId>org.apache.maven.plugins</groupId>
69+
<artifactId>maven-compiler-plugin</artifactId>
70+
<configuration>
71+
<release>${maven.compiler.release}</release>
72+
<compilerArgs>
73+
<arg>--enable-preview</arg>
74+
<arg>-Xlint:all</arg>
75+
<arg>-Werror</arg>
76+
</compilerArgs>
77+
</configuration>
78+
</plugin>
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-surefire-plugin</artifactId>
82+
<configuration>
83+
<argLine>--enable-preview</argLine>
84+
</configuration>
85+
</plugin>
86+
</plugins>
87+
</build>
88+
</project>

0 commit comments

Comments
 (0)