|
| 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