Skip to content

fix: use reflection to access MavenId to avoid binary incompatibility with IDEA 261+#617

Merged
tangcent merged 1 commit intomasterfrom
fix/mavenid-reflection-compatibility
Mar 17, 2026
Merged

fix: use reflection to access MavenId to avoid binary incompatibility with IDEA 261+#617
tangcent merged 1 commit intomasterfrom
fix/mavenid-reflection-compatibility

Conversation

@tangcent
Copy link
Copy Markdown
Owner

This PR cherry-picks a fix from easy-yapi to address binary incompatibility with IntelliJ IDEA 261+.

Changes

  • Use reflection to access MavenId to avoid binary incompatibility with IDEA 261+

Source

Cherry-picked from easy-yapi commit f99e8c7c

Related

… with IDEA 261+ (#1279)

MavenHelper.getMavenIdByMaven() previously accessed mavenProject.mavenId
directly, which references org.jetbrains.idea.maven.model.MavenId in its
bytecode. In IntelliJ IDEA IU-261.22158.121, this class is no longer
resolvable from the plugin classloader, causing NoSuchClassError at runtime.

This fix uses reflection to call getMavenId() and access groupId/artifactId/
version properties, avoiding any compile-time reference to the MavenId class.
@github-actions github-actions bot added the type: bug Something isn't working label Mar 16, 2026
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Use reflection to access MavenId for IDEA 261+ compatibility

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Use reflection to access MavenId avoiding binary incompatibility
• Resolves NoSuchClassError in IntelliJ IDEA 261+ versions
• Prevents direct compile-time reference to MavenId class
• Maintains backward compatibility with earlier IDEA versions
Diagram
flowchart LR
  A["Direct MavenId access"] -->|"causes NoSuchClassError"| B["IDEA 261+ incompatibility"]
  C["Reflection-based access"] -->|"avoids compile-time reference"| D["Compatible with all versions"]
  A -.->|"replaced by"| C
Loading

Grey Divider

File Changes

1. idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt 🐞 Bug fix +11/-4

Replace direct MavenId access with reflection

• Replaced direct property access with reflection-based method invocation
• Changed mavenProject.mavenId to use getMethod("getMavenId").invoke()
• Changed direct property access for groupId, artifactId, version to reflection calls
• Added documentation explaining the reflection approach for compatibility

idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Reflection failures unlogged 🐞 Bug ✧ Quality
Description
In MavenHelper.getMavenIdByMaven, reflective calls to getMavenId/getGroupId/getArtifactId/getVersion
can fail via reflection exceptions or unexpected return types, and the method simply returns null.
Because getMavenId wraps this path in safe{ } and there is no logging, Maven ID resolution can
silently degrade to the Gradle fallback or null with no diagnostic signal.
Code

idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[R48-53]

+        // Use reflection to access mavenId and its properties to avoid direct dependency on
+        // org.jetbrains.idea.maven.model.MavenId which may not be resolvable in some IDEA versions
+        val mavenId = mavenProject.javaClass.getMethod("getMavenId").invoke(mavenProject) ?: return null
+        val groupId = mavenId.javaClass.getMethod("getGroupId").invoke(mavenId) as? String ?: return null
+        val artifactId = mavenId.javaClass.getMethod("getArtifactId").invoke(mavenId) as? String ?: return null
+        val version = mavenId.javaClass.getMethod("getVersion").invoke(mavenId) as? String ?: return null
Evidence
The updated Maven lookup is done entirely via reflection and returns null on any missing/invalid
value, while the public getMavenId call site wraps the Maven path in safe{ } and provides no logging
or error reporting. This combination makes reflection failures effectively invisible in production.

idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[25-30]
idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[48-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`MavenHelper.getMavenIdByMaven` relies on reflection for MavenId access. If any reflective lookup/invocation fails, the code returns `null` and (via `getMavenId`) silently falls back to Gradle or returns `null`, with no logs. This makes Maven-coordinate resolution failures hard to debug.

### Issue Context
This PR intentionally uses reflection to avoid binary incompatibility across IntelliJ versions/classloaders. Reflection failures are expected in some environments; they should be observable.

### Fix Focus Areas
- idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[41-58]

### Suggested fix
Add a logger to `MavenHelper` and wrap the reflective access in `try { ... } catch (e: ReflectiveOperationException) { LOG.debug/warn(..., e); return null }` (optionally also catch broader `Throwable` if you want to avoid plugin crashes from linkage-related errors in this compatibility code path).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

2. Repeated reflective lookups 🐞 Bug ➹ Performance
Description
getMavenIdByMaven performs multiple Class.getMethod lookups on every invocation, even though the
method set is stable for a given runtime class. This adds avoidable overhead and makes the code more
expensive to run if Maven ID resolution is called repeatedly.
Code

idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[R50-53]

+        val mavenId = mavenProject.javaClass.getMethod("getMavenId").invoke(mavenProject) ?: return null
+        val groupId = mavenId.javaClass.getMethod("getGroupId").invoke(mavenId) as? String ?: return null
+        val artifactId = mavenId.javaClass.getMethod("getArtifactId").invoke(mavenId) as? String ?: return null
+        val version = mavenId.javaClass.getMethod("getVersion").invoke(mavenId) as? String ?: return null
Evidence
The implementation performs four separate reflective lookups (getMethod) and invocations for every
call instead of caching methods/handles per runtime class.

idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[50-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getMavenIdByMaven` does repeated `Class.getMethod(...)` calls for the same method names each time it runs. This is avoidable overhead.

### Issue Context
Because this code exists for cross-version compatibility, keep caching scoped per runtime `Class<?>` (to respect different classloaders) and prefer weak keys to avoid retaining classloaders.

### Fix Focus Areas
- idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt[41-58]

### Suggested fix
Introduce a small cache like `private val mavenProjectMethods = ConcurrentHashMap<Class<*>, Method>()` (or a WeakHashMap wrapper) and similarly for MavenId getters, then reuse those `Method` instances for invocation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@github-actions
Copy link
Copy Markdown
Contributor

📦 Plugin has been packaged for this PR. You can download easy-api-2.4.3.212.0.zip from the GitHub Actions workflow run by clicking on the "Artifacts" dropdown.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 42.85714% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.987%. Comparing base (29838cf) to head (fa501c3).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
...ain/kotlin/com/itangcent/idea/utils/MavenHelper.kt 42.857% 0 Missing and 4 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@               Coverage Diff               @@
##              master      #617       +/-   ##
===============================================
+ Coverage     53.908%   53.987%   +0.079%     
+ Complexity      2366      2350       -16     
===============================================
  Files            259       259               
  Lines          14699     13544     -1155     
  Branches        3248      3256        +8     
===============================================
- Hits            7924      7312      -612     
+ Misses          5331      4787      -544     
- Partials        1444      1445        +1     
Flag Coverage Δ
unittests 53.987% <42.857%> (+0.079%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ain/kotlin/com/itangcent/idea/utils/MavenHelper.kt 88.333% <42.857%> (-6.404%) ⬇️

... and 160 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 29838cf...fa501c3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tangcent tangcent merged commit 14ec98b into master Mar 17, 2026
15 checks passed
@tangcent tangcent deleted the fix/mavenid-reflection-compatibility branch March 17, 2026 00:11
@github-actions github-actions bot mentioned this pull request Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant