Skip to content

Commit 202a108

Browse files
committed
Add review for PR mapstruct#3911: ImplementationNamingStrategy SPI
https://claude.ai/code/session_01LGYdWYMZiNx38Hbx22wY72
1 parent 5ad9cf9 commit 202a108

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

PR_3911_REVIEW.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
## Review of PR #3911: Add SPI for Implementation Naming (#3619)
2+
3+
### Architecture & Design Issues
4+
5+
#### 1. SPI should be loaded in `AnnotationProcessorContext`, not statically in `Mapper`/`Decorator` (Critical)
6+
7+
The PR loads the SPI via a `static final` field directly in `Mapper.java` and `Decorator.java`:
8+
9+
```java
10+
static final ImplementationNamingStrategy IMPLEMENTATION_NAMING_STRATEGY =
11+
Services.get( ImplementationNamingStrategy.class, new DefaultImplementationNamingStrategy() );
12+
```
13+
14+
**This breaks the established pattern.** Every other SPI in the project is loaded lazily in `AnnotationProcessorContext.initialize()`. The reasons for this pattern are documented in the codebase:
15+
16+
> *"The reason why we do this is due to the fact that when custom SPI implementations are done and users don't set `proc:none` then our processor would be triggered. And this context will always get initialized and the SPI won't be found."*
17+
18+
Loading statically in `Mapper`/`Decorator` bypasses this lazy initialization safety. The strategy should be loaded in `AnnotationProcessorContext`, stored as a field, and passed through to `Mapper.Builder` / `Decorator.Builder`.
19+
20+
#### 2. Missing `init(MapStructProcessingEnvironment)` method (Critical)
21+
22+
Every SPI interface in MapStruct provides a default `init()` method:
23+
24+
```java
25+
default void init(MapStructProcessingEnvironment processingEnvironment) { }
26+
```
27+
28+
This is present in `AccessorNamingStrategy`, `BuilderProvider`, `EnumMappingStrategy`, etc. The new `ImplementationNamingStrategy` interface is missing this. SPI implementations may need access to `Elements`/`Types` utilities (e.g., to do more sophisticated naming based on type hierarchy), and omitting `init()` would require a breaking change to add it later.
29+
30+
#### 3. Missing verbose logging (Minor)
31+
32+
All other SPIs log which implementation is active when `verbose` mode is enabled:
33+
34+
```java
35+
if (verbose) {
36+
messager.printMessage(Diagnostic.Kind.NOTE,
37+
"MapStruct: Using implementation naming strategy: " + ...);
38+
}
39+
```
40+
41+
This is absent from the PR.
42+
43+
#### 4. Duplicate `Services.get()` calls (Bug)
44+
45+
Both `Mapper.java` and `Decorator.java` independently call `Services.get()`. Since `Services.get()` creates a new `ServiceLoader` each time, this is wasteful and could theoretically produce inconsistent results if the classpath changes between calls. The strategy should be loaded once and shared.
46+
47+
---
48+
49+
### Interface Design Issues
50+
51+
#### 5. Confusing parameter semantics (Important)
52+
53+
The interface methods take `(String className, String implementationName)` where:
54+
- `className` = the flat name of the mapper interface (e.g., `"CarMapper"`)
55+
- `implementationName` = the already-resolved implementation name (e.g., `"CarMapperImpl"`)
56+
57+
This means the SPI receives the *result* of the `<CLASS_NAME>` placeholder replacement. The SPI user cannot access the raw `implementationName` expression. The parameter names and Javadoc should be clearer about what has already been resolved vs. what hasn't.
58+
59+
Suggestion: rename `implementationName` to something like `resolvedImplementationName` or clarify in Javadoc that placeholder resolution has already occurred.
60+
61+
#### 6. Missing `@since` and `@author` tags (Minor)
62+
63+
All existing SPI interfaces include `@author` and `@since` tags in their Javadoc. Both the interface and the default implementation should include these.
64+
65+
#### 7. Consider `@Experimental` annotation (Minor)
66+
67+
`MappingExclusionProvider` uses `@Experimental("This SPI can have its signature changed in subsequent releases")`. Since this is a new SPI whose API may evolve, it would be worth considering the same annotation here.
68+
69+
---
70+
71+
### Implementation Issues
72+
73+
#### 8. `hasCustomName` logic is fragile (Important)
74+
75+
The PR changes how `customName` is determined in `Mapper.java`. The original code checks whether the annotation attribute differs from the default:
76+
```java
77+
this.customName = !DEFAULT_IMPLEMENTATION_CLASS.equals(this.implName);
78+
```
79+
80+
The PR replaces this with a post-hoc check that compares the *SPI-transformed* name against the *default pre-SPI* name. If a user has a custom `ImplementationNamingStrategy` but uses the default `implementationName` annotation attribute, `hasCustomName` will return `true` even though the user didn't set a custom name via `@Mapper(implementationName=...)`. This affects `hasCustomImplementation()` which is used for `Mappers.getMapper()` lookup logic. The "custom name" concept should reflect whether the *annotation* specifies a custom name, not whether the SPI changed it.
81+
82+
#### 9. Decorator naming logic is unclear (Important)
83+
84+
In the current codebase, `Decorator.build()` generates the decorator class name and delegate field name (`implementationName + "_"`). The PR introduces `generateDecoratorImplementationName()` and `generateMapperImplementationName()` in `Decorator.Builder`, which is confusing - the Decorator builder shouldn't need to generate "mapper" implementation names. The naming of these methods within `Decorator` needs rethinking.
85+
86+
---
87+
88+
### Test Issues
89+
90+
#### 10. Package name uses uppercase (Style)
91+
92+
```java
93+
package org.mapstruct.ap.test.naming.spi.ImplementationNamingStrategy;
94+
```
95+
96+
Java package names should be all lowercase. This should be something like:
97+
```java
98+
package org.mapstruct.ap.test.naming.spi.implementationnamingstrategy;
99+
```
100+
101+
#### 11. Test doesn't verify generated source code (Important)
102+
103+
Other SPI tests use `GeneratedSource` to verify the actual generated code. The new tests only verify runtime behavior (`getClass().getSimpleName()`). They should also verify the generated `.java` file has the correct class name.
104+
105+
#### 12. Test hardcodes naming logic (Minor)
106+
107+
The test creates a `CustomImplementationNamingStrategy` and calls its methods to compute the expected name. It would be cleaner to hardcode the expected result directly, making the test more readable and independent.
108+
109+
---
110+
111+
### Documentation Issues
112+
113+
#### 13. Documentation example doesn't match a realistic use case (Minor)
114+
115+
The example `CustomImplementationNamingStrategy` appends `"MapperImpl"` to the already-resolved name, producing names like `CarMapperImplMapperImpl`. The issue #3619 describes a more practical use case (e.g. stripping a prefix). The documentation example should demonstrate the actual motivating use case.
116+
117+
---
118+
119+
### Summary of Required Changes
120+
121+
| Priority | Issue |
122+
|----------|-------|
123+
| Critical | Load SPI in `AnnotationProcessorContext`, not statically in model classes |
124+
| Critical | Add `default void init(MapStructProcessingEnvironment)` to the interface |
125+
| Important | Fix `hasCustomName` logic to not conflate SPI naming with annotation-based naming |
126+
| Important | Clarify decorator vs. mapper naming in `Decorator.Builder` |
127+
| Important | Add generated source verification to tests |
128+
| Important | Clarify parameter semantics in the SPI interface |
129+
| Style | Fix uppercase package name in tests |
130+
| Minor | Add `@since`, `@author`, verbose logging, consider `@Experimental` |
131+
| Minor | Improve documentation example to match the motivating use case |
132+
133+
Overall this is a good start at addressing a real user need, but the integration approach needs to follow the established SPI patterns in the codebase before it's ready to merge.

0 commit comments

Comments
 (0)