Skip to content

Commit 29eb89f

Browse files
committed
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
1 parent 96729c2 commit 29eb89f

File tree

11 files changed

+2034
-0
lines changed

11 files changed

+2034
-0
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 21
18+
uses: actions/setup-java@v4
19+
with:
20+
java-version: '21'
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+
});

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ target/
44

55
.claude/
66
.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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,74 @@
1515

1616
<name>API Tracker</name>
1717

18+
<properties>
19+
<maven.compiler.release>24</maven.compiler.release>
20+
</properties>
21+
1822
<dependencies>
1923
<dependency>
2024
<groupId>io.github.simbo1905.json</groupId>
2125
<artifactId>json-java21</artifactId>
2226
<version>${project.version}</version>
2327
</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>
2444
</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>
2588
</project>

0 commit comments

Comments
 (0)