8000 feat(graalvm): auto-register controller parameter and return types for reflection by kawasima · Pull Request #156 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

feat(graalvm): auto-register controller parameter and return types for reflection - #156

Merged
kawasima merged 8 commits into
developfrom
feature/issue-150-auto-register-param-types
Apr 4, 2026
Merged

kawasima merged 8 commits into
developfrom
feature/issue-150-auto-register-param-types

Conversation

@kawasima
@kawasima kawasima commented Apr 4, 2026 •
Copy link
Copy Markdown
Collaborator

Summary

  • KotowariFeature.registerControllerReflection() now traverses all public controller methods and auto-registers parameter types and return types (including generic type arguments such as List<Customer>) for reflection
  • Field types are traversed recursively (cycle-guarded by the result set) and superclass chains are followed, so nested DTOs and inherited fields are fully covered
  • Types in java.*, javax.*, jakarta.*, jdk.*, sun.*, com.sun.*, enkan.*, and specific kotowari framework sub-packages (kotowari.graalvm.*, kotowari.routing.*, kotowari.middleware.*, kotowari.inject.*, kotowari.data.*, kotowari.component.*, kotowari.io.*, kotowari.scope.*, kotowari.system.*, kotowari.util.*) are skipped. Application types under kotowari.example.* or similar are not skipped and are correctly auto-registered.
  • The example app's reachability-metadata.json is reduced to only the two build-time factory entries (NativeApplicationFactory, NativeSystemFactory) — no more manual form/model/controller entries needed

Test plan

  • collectReachableTypes_includesAppClassAndFieldTypesTransitively — verifies SimpleForm (extends BaseForm) collects BaseForm and Address via superclass traversal
  • collectReachableTypes_traversesGenericTypeArguments — verifies List<Address> return type collects Address
  • collectReachableTypes_excludesEnkanFrameworkTypes — verifies enkan.* is excluded
  • shouldSkipType_skipsKotowariFrameworkPackages — verifies all kotowari framework sub-packages are skipped
  • shouldSkipType_doesNotSkipAppClass — verifies app.example.* is not skipped
  • All existing KotowariFeatureTest tests still pass

Closes #150

🤖 Generated with Claude Code

kawasima and others added 3 commits April 4, 2026 13:53
…r reflection

KotowariFeature.registerControllerReflection() now traverses all public
controller methods and collects their parameter types and return types,
then registers them for reflection automatically. Application-level types
(form objects, DTOs, models) no longer need manual entries in
reachability-metadata.json.

Types in java.*, javax.*, jakarta.*, sun.*, enkan.*, and kotowari.*
packages are skipped (covered by built-in analysis or other features).
Field types are collected one level deep to handle nested objects.

The example app's reachability-metadata.json is reduced to only the two
build-time factory entries (NativeApplicationFactory, NativeSystemFactory).

Closes #150

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stration

- Fix Javadoc for collectReachableTypes: clarify that recursion is
  unbounded (cycle-guarded by the result set), not limited to one level
- Add jdk.* to shouldSkipType filter alongside existing java.*/sun.* entries
- Add recursion test: SimpleForm.Address nested field is collected transitively
- Replace HttpRequest (interface) with DefaultHttpRequest (concrete enkan.*
  class) in exclusion test for clearer intent
- Add explicit jdk.* exclusion test using Class.forName to avoid
  compile-time accessibility issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e tests

- Make shouldSkipType package-private so it can be tested directly,
  removing the need for Class.forName on jdk-internal types
- Replace jdk.internal.loader.BuiltinClassLoader (implementation-dependent)
  with jdk.net.ExtendedSocketOptions (stable public API) for jdk.* filter test
- Merge redundant collectReachableTypes_includesAppClass into
  collectReachableTypes_includesAppClassAndFieldTypesTransitively

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances kotowari-graalvm’s KotowariFeature to automatically register controller method parameter/return types (and their reachable field types) for GraalVM reflection, aiming to remove the need for manual reachability-metadata.json entries in apps (including the GraalVM example).

Changes:

  • Auto-collect and register controller method parameter and return types for reflection during GraalVM analysis.
  • Add unit tests and small test fixtures to validate type collection and skip rules.
  • Reduce the GraalVM example’s reachability-metadata.json by removing manual DTO/controller reflection entries.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
kotowari-graalvm/src/main/java/kotowari/graalvm/KotowariFeature.java Adds reachable-type collection + skip rules and registers collected types for reflection.
kotowari-graalvm/src/test/java/kotowari/graalvm/KotowariFeatureTest.java Adds tests for reachable-type collection and skip behavior.
kotowari-graalvm/src/test/java/kotowari/graalvm/controller/SimpleController.java Adds a method using an app DTO to exercise parameter/return collection.
kotowari-graalvm/src/test/java/app/example/SimpleForm.java New DTO fixture for tests.
kotowari-graalvm/src/test/java/app/example/Address.java Nested DTO fixture for transitive field-type collection.
kotowari-graalvm-example/src/main/resources/.../reachability-metadata.json Removes manual reflection entries relying on new auto-registration behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +229 to +238
static boolean shouldSkipType(Class<?> type) {
String name = type.getName();
return name.startsWith("java.")
|| name.startsWith("javax.")
|| name.startsWith("jakarta.")
|| name.startsWith("jdk.")
|| name.startsWith("sun.")
|| name.startsWith("com.sun.")
|| name.startsWith("enkan.")
|| name.startsWith("kotowari.");
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldSkipType currently skips all types under the kotowari. package prefix. This will also skip application/example types that live under kotowari.example.* (e.g. kotowari.example.graalvm.form.TodoForm, kotowari.example.entity.Customer), so controller parameter/return DTOs won’t be auto-registered and the native image will likely fail at runtime. Consider narrowing the skip rule to Kotowari framework packages (e.g. kotowari.graalvm.*, kotowari.routing.*, etc.) or making the skipped prefixes configurable, rather than skipping the entire kotowari. namespace.

8000

Copilot uses AI. Check for mistakes.
Comment on lines +192 to +198
for (Method m : ctrl.getDeclaredMethods()) {
if (!Modifier.isPublic(m.getModifiers())) continue;
for (Parameter p : m.getParameters()) {
collectReachableTypes(p.getType(), appTypes);
}
collectReachableTypes(m.getReturnType(), appTypes);
}
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only inspects raw parameter/return types via p.getType() / m.getReturnType(). For signatures like List<Customer> list() or Optional<MyDto> get(), the reachable DTO type(s) are in generic arguments and won’t be collected (e.g. CustomerController.list() in kotowari-example returns List<Customer>). Consider traversing getGenericParameterTypes() / getGenericReturnType() and extracting ParameterizedType arguments (and possibly wildcard bounds) so container return types don’t cause missed reflection registrations.

Copilot uses AI. Check for mistakes.
@@ -9,27 +9,6 @@
"type": "kotowari.example.graalvm.NativeSystemFactory",
"allDeclaredMethods": true,
"allDeclaredConstructors": true
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current shouldSkipType implementation skipping the entire kotowari.* namespace, the removed reflection entries for kotowari.example.graalvm.{controller/model/form}.* will not be replaced by auto-registration, so the example native build is likely to regress. Either keep these entries until the skip logic is narrowed, or adjust shouldSkipType so application types under kotowari.example.* are included.

Suggested change
"allDeclaredConstructors": true
"allDeclaredConstructors": true
},
{
"condition": {
"typeReached": "kotowari.example.graalvm.controller.*"
},
"type": "kotowari.example.graalvm.controller.*",
"allDeclaredMethods": true,
"allDeclaredConstructors": true
},
{
"condition": {
"typeReached": "kotowari.example.graalvm.model.*"
},
"type": "kotowari.example.graalvm.model.*",
"allDeclaredMethods": true,
"allDeclaredConstructors": true
},
{
"condition": {
"typeReached": "kotowari.example.graalvm.form.*"
},
"type": "kotowari.example.graalvm.form.*",
"allDeclaredMethods": true,
"allDeclaredConstructors": true

Copilot uses AI. Check for mistakes.
import kotowari.graalvm.controller.SimpleController;
import app.example.Address;
import app.example.SimpleForm;
import enkan.data.DefaultHttpRequest;
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant duplicate import of enkan.data.DefaultHttpRequest (already imported earlier). Please remove the extra import to avoid lint/IDE warnings and keep the import block clean.

Suggested change
import enkan.data.DefaultHttpRequest;

Copilot uses AI. Check for mistakes.
kawasima and others added 4 commits April 4, 2026 14:56
- Narrow shouldSkipType from kotowari.* to specific framework subpackages
  (kotowari.graalvm.*, kotowari.routing.*, kotowari.middleware.*,
  kotowari.inject.*, kotowari.data.*) so application types under
  kotowari.example.* are correctly auto-registered
- Traverse generic type arguments (ParameterizedType, WildcardType) so
  container return types like List<Customer> register their type arguments
- Remove duplicate import of enkan.data.DefaultHttpRequest in test file
- Add tests: generic type argument traversal, kotowari framework package
  skip, kotowari app package inclusion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use getMethods() instead of getDeclaredMethods() to include action
  methods inherited from controller superclasses; skip Object methods
- Guard getDeclaredFields() traversal with SecurityException catch to
  handle module-restricted types gracefully
- Add Javadoc to shouldSkipType() explaining why only specific kotowari
  sub-packages are listed and what to do when new ones are added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…egisterAllMethods

- Add missing kotowari framework sub-packages to shouldSkipType:
  kotowari.component.*, kotowari.io.*, kotowari.scope.*,
  kotowari.system.*, kotowari.util.*
- Skip framework-declaring methods early in getMethods() loop to avoid
  unnecessary type traversal for Validatable/Extendable default methods
- Add RuntimeReflection.registerAllMethods(ctrl) alongside
  registerAllDeclaredMethods so inherited public action methods on
  controllers are registered for reflection in native image
- Expand shouldSkipType test to cover all kotowari framework sub-packages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- collectReachableTypes: traverse superclass chain so field types declared
  in a base class (e.g. BaseForm.address) are collected transitively;
  each superclass itself is also added to result for reflection registration
- appTypes registration: use registerAllFields/registerAllMethods instead
  of registerAllDeclaredFields/registerAllDeclaredMethods so inherited
  getters/setters and fields from superclasses are covered
- Add BaseForm test fixture; extend SimpleForm from BaseForm so the
  superclass traversal test is realistic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +228 to +232
* <p>Handles raw classes, arrays, parameterized types (e.g. {@code List<Customer>}),
* and wildcard bounds. Recursively traverses declared field types to handle nested
* objects (e.g. embedded DTOs). Cycles are prevented by the {@code result} set: once
* a type is added it is not visited again, so traversal terminates even for
* mutually-referential types.
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says field types are collected “one level deep”, but collectReachableTypes() is fully recursive (it traverses reachable field types transitively). This can significantly increase the set of reflection-registered classes and native image size; either update the PR description to match the actual behavior or add an explicit depth/limit to the traversal to keep it bounded.

Copilot uses AI. Check for mistakes.
Comment on lines +249 to +255
try {
for (Field f : current.getDeclaredFields()) {
collectReachableTypes(f.getGenericType(), result);
}
} catch (SecurityException e) {
// Module system may deny field access for some types; skip this class
}
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collectReachableTypes() swallows SecurityException when scanning declared fields, which can silently skip nested types and lead to missing reflection registrations at runtime. Consider logging (at least the class name being skipped) or surfacing this in a controlled way so failures are diagnosable during native-image builds.

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +277
< 10000 td data-line-number="271" class="blob-num blob-num-addition">
/**
* Returns {@code true} for types that do not need manual reflection registration:
* JDK types, framework types (enkan.* and known kotowari framework sub-packages).
*
* <p>Note: only specific kotowari framework sub-packages are listed here rather than
* the entire {@code kotowari.} namespace, so that application types under
* {@code kotowari.example.*} or similar are correctly auto-registered. If new
* framework sub-packages are added to kotowari they must be listed here explicitly.
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states that all types under kotowari.* are skipped, but shouldSkipType() intentionally only skips specific kotowari framework sub-packages (and allows application packages like kotowari.example.*). Please update the PR description (or adjust the implementation) so documented behavior matches what the code actually does.

Copilot uses AI. Check for mistakes.
Replace silent swallow with System.err output so native-image build logs
show which class triggered the module access denial, aiding diagnosis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kawasima
kawasima merged commit 8118b68 into develop Apr 4, 2026
1 check passed
@kawasima
kawasima deleted the feature/issue-150-auto-register-param-types branch April 4, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0