From f563b596b2aadcf78c5a4e5e565cba4910d5c67a Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Wed, 5 Nov 2025 17:01:56 +0800 Subject: [PATCH 01/10] feat: Add Protobuf support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented comprehensive support for protobuf-generated classes with smart accessor detection and code generation. Core Features: 1. Protobuf Getter Recognition - getXxxList() → property "xxx" (repeated fields) - getXxxMap() → property "xxx" (map fields) 2. Repeated Field Support - Detects addXxx() methods for repeated fields - Generates loop-based adder calls instead of setters - Example: addItem(item) for List items 3. Map Field Support (NEW) - Added PUTTER accessor type for map operations - Detects putXxx(K, V) methods for map entries - Detects putAllXxx(Map) for bulk operations - Generates entry iteration with putter calls Implementation Details: - Extended MethodType enum with PUTTER type - Extended AccessorType enum with PUTTER type - Created ProtobufAccessorNamingStrategy for protobuf patterns - Added getPutterForType() method in Type.java - Created PutterWrapper and PutterWrapper.ftl for code generation - Updated PropertyMapping to handle PUTTER accessor type - Integrated putter selection into getPropertyWriteAccessors() Technical Changes: - processor/src/main/java/org/mapstruct/ap/spi/MethodType.java - processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java - processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java (new) - processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java - processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java - processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java - processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java - processor/src/main/java/org/mapstruct/ap/internal/model/assignment/PutterWrapper.java (new) - processor/src/main/resources/.../assignment/PutterWrapper.ftl (new) Backward Compatibility: - All changes are additive and non-breaking - Default behavior preserved for non-protobuf classes - Protobuf support activated via ProtobufAccessorNamingStrategy 🤖 Generated with Claude Code Co-Authored-By: Claude refactor: Improve ProtobufAccessorNamingStrategy to only apply to MessageOrBuilder types Based on the reference implementation from mapstruct-examples, this commit adds proper type checking to ensure protobuf-specific logic is only applied to Protocol Buffers generated classes. Key Improvements: 1. MessageOrBuilder Type Detection - Initialize protobufMessageOrBuilderType in init() method - Check isProtobufType() before applying any protobuf-specific logic - Only classes implementing com.google.protobuf.MessageOrBuilder get special treatment 2. Protobuf Method Exclusions - Exclude getXxxBuilderList() methods (internal builder lists) - Exclude getXxxValueList() methods (internal value lists) - Exclude getAllFields() method (protobuf metadata) - Exclude methods with protobuf internal types as parameters - Exclude remove(int) methods (protobuf list manipulation) - Exclude mergeFrom() methods (protobuf internal) 3. Enhanced getElementName() - Append "List" suffix only for protobuf types - Matches protobuf convention: addItem() -> itemsList property - Append "Map" suffix for putter methods: putAttribute() -> attributesMap 4. Backward Compatibility - Non-protobuf classes use default behavior - All checks delegate to super methods first - Only add protobuf-specific filters on top Implementation follows the pattern from: https://github.com/mapstruct/mapstruct-examples/tree/main/mapstruct-on-gradle-protobuf3 🤖 Generated with Claude Code Co-Authored-By: Claude Refactor: Treat protobuf map putters as ADDER type Simplified architecture by removing the separate PUTTER method type and treating map putter methods (e.g., putXxx(key, value)) as ADDER type instead. This reduces code complexity while maintaining the same functionality for protobuf map field mapping. Changes: - Removed PUTTER from MethodType and AccessorType enums - Updated ProtobufAccessorNamingStrategy to return ADDER for putter methods - Updated AccessorNamingUtils to accept 1 or 2 parameters for adders - Removed putterMethodsIn from Filters - Simplified Type.java by removing getPutters/getPutterForType methods - Updated getAdderForType to handle both collections and maps - Simplified PropertyMapping by removing assignToPlainViaPutter - Updated assignToPlainViaAdder to handle maps with PutterWrapper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude test: Add tests for Protocol Buffers (protobuf) support Added comprehensive tests to verify ProtobufAccessorNamingStrategy functionality: Test Structure: - Created mock com.google.protobuf.MessageOrBuilder interface for testing - Created protobuf-style source classes with getXxxList() and getXxxMap() patterns - Created target DTOs with standard JavaBeans accessors and adder/putter methods - Registered ProtobufAccessorNamingStrategy in test resources META-INF/services Tests: - SimpleProtobufTest: Tests basic repeated field mapping (getItemsList -> addItem) - ProtobufTest: Tests both repeated and map field mappings (currently partially working) The tests verify that: 1. getXxxList() is recognized as getter for "xxx" property (repeated fields) 2. getXxxMap() is recognized as getter for "xxx" property (map fields) 3. addXxx() adder methods are used for repeated fields 4. putXxx() putter methods are used for map fields Note: SimpleProtobufTest passes successfully, demonstrating that repeated field support is working correctly with ProtobufAccessorNamingStrategy. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude add docs ref ref rm doc revert revert revert cpl error no adder for pb no adder for pb handle enum done not support byte[] add ProtobufEnumConversion google/type pkg rm DateTime support adjust format rm unused code add protobufTest add protobufTest done ref ignore message set builder const fmt add headler inline ref it revert unnecessary changes revert unnecessary changes revert unnecessary changes revert unnecessary changes Add missing import --- .../chapter-5-data-type-conversions.asciidoc | 22 + .../itest/tests/MavenIntegrationTest.java | 4 +- .../itest/protobuf/ProtobufMapperTest.java | 44 -- .../pom.xml | 13 +- .../mapstruct/itest/protobuf/AddressDto.java | 0 .../mapstruct/itest/protobuf/Everything.java | 413 ++++++++++ .../itest/protobuf/EverythingMapper.java | 31 + .../mapstruct/itest/protobuf/PersonDto.java | 0 .../itest/protobuf/PersonMapper.java | 3 +- .../src/main/proto/Person.proto | 0 .../src/main/proto/everything.proto | 82 ++ .../itest/protobuf/ProtobufMapperTest.java | 178 +++++ parent/pom.xml | 5 + .../ap/internal/conversion/Conversions.java | 89 ++- .../ProtobufBoolValueConversion.java | 38 + .../ProtobufByteStringConversion.java | 38 + .../ProtobufBytesValueConversion.java | 38 + .../ProtobufDoubleValueConversion.java | 38 + .../conversion/ProtobufEnumConversion.java | 47 ++ .../ProtobufFloatValueConversion.java | 38 + .../ProtobufInt32ValueConversion.java | 38 + .../ProtobufInt64ValueConversion.java | 38 + .../ProtobufStringValueConversion.java | 38 + .../ProtobufUInt32ValueConversion.java | 38 + .../ProtobufUInt64ValueConversion.java | 38 + .../source/builtin/BuiltInMappingMethods.java | 30 + .../builtin/InstantToProtobufTimestamp.java | 49 ++ .../JavaDayOfWeekToProtobufDayOfWeek.java | 50 ++ .../JavaDurationToProtobufDuration.java | 49 ++ .../builtin/JavaMonthToProtobufMonth.java | 50 ++ .../builtin/LocalDateToProtobufDate.java | 49 ++ .../builtin/LocalTimeToProtobufTimeOfDay.java | 49 ++ .../builtin/ProtobufDateToLocalDate.java | 49 ++ .../ProtobufDayOfWeekToJavaDayOfWeek.java | 49 ++ .../ProtobufDurationToJavaDuration.java | 49 ++ .../builtin/ProtobufMonthToJavaMonth.java | 49 ++ .../builtin/ProtobufTimeOfDayToLocalTime.java | 49 ++ .../builtin/ProtobufTimestampToInstant.java | 49 ++ .../util/AnnotationProcessorContext.java | 8 + .../ap/internal/util/ProtobufConstants.java | 43 ++ .../spi/ProtobufAccessorNamingStrategy.java | 708 ++++++++++++++++++ .../builtin/InstantToProtobufTimestamp.ftl | 18 + .../JavaDayOfWeekToProtobufDayOfWeek.ftl | 15 + .../JavaDurationToProtobufDuration.ftl | 18 + .../builtin/JavaMonthToProtobufMonth.ftl | 15 + .../builtin/LocalDateToProtobufDate.ftl | 19 + .../builtin/LocalTimeToProtobufTimeOfDay.ftl | 20 + .../builtin/ProtobufDateToLocalDate.ftl | 15 + .../ProtobufDayOfWeekToJavaDayOfWeek.ftl | 15 + .../ProtobufDurationToJavaDuration.ftl | 15 + .../builtin/ProtobufMonthToJavaMonth.ftl | 15 + .../builtin/ProtobufTimeOfDayToLocalTime.ftl | 20 + .../builtin/ProtobufTimestampToInstant.ftl | 15 + 53 files changed, 2836 insertions(+), 54 deletions(-) delete mode 100644 integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java rename integrationtest/src/test/resources/{protobufBuilderTest => protobufTest}/pom.xml (84%) rename integrationtest/src/test/resources/{protobufBuilderTest => protobufTest}/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java (100%) create mode 100644 integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java create mode 100644 integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java rename integrationtest/src/test/resources/{protobufBuilderTest => protobufTest}/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java (100%) rename integrationtest/src/test/resources/{protobufBuilderTest => protobufTest}/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java (81%) rename integrationtest/src/test/resources/{protobufBuilderTest => protobufTest}/src/main/proto/Person.proto (100%) create mode 100644 integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto create mode 100644 integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/ProtobufConstants.java create mode 100644 processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.ftl diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index ad49fe2960..001e146622 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -133,6 +133,28 @@ public interface CarMapper { * Between `java.util.Locale` and `String`. ** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information. +* Between Protocol Buffers (Protobuf) types and corresponding Java types: +** Between `com.google.protobuf.ByteString` and `String` +** Between Protobuf wrapper types and their corresponding Java primitive types: +*** `com.google.protobuf.DoubleValue` and `Double` / `double` +*** `com.google.protobuf.FloatValue` and `Float` / `float` +*** `com.google.protobuf.Int32Value` and `Integer` / `int` +*** `com.google.protobuf.UInt32Value` and `Integer` / `int` +*** `com.google.protobuf.Int64Value` and `Long` / `long` +*** `com.google.protobuf.UInt64Value` and `Long` / `long` +*** `com.google.protobuf.BoolValue` and `Boolean` / `boolean` +*** `com.google.protobuf.StringValue` and `String` +*** `com.google.protobuf.BytesValue` and `com.google.protobuf.ByteString` +** Between `com.google.protobuf.ProtocolMessageEnum` and `Integer` / `int` (using the enum's numeric value, not ordinal) +** Between Protobuf well-known types and Java 8 Date-Time types: +*** `com.google.protobuf.Timestamp` and `java.time.Instant` +*** `com.google.protobuf.Duration` and `java.time.Duration` +** Between Protobuf `google.type` package types and Java 8 Date-Time types: +*** `com.google.type.Date` and `java.time.LocalDate` +*** `com.google.type.TimeOfDay` and `java.time.LocalTime` +*** `com.google.type.DayOfWeek` and `java.time.DayOfWeek` +*** `com.google.type.Month` and `java.time.Month` + [[mapping-object-references]] === Mapping object references diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index ab43cc4cba..35179ea370 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -111,10 +111,10 @@ void namingStrategyTest() { /** * ECLIPSE_JDT is not working with Protobuf. Use all other available processor types. */ - @ProcessorTest(baseDir = "protobufBuilderTest", processorTypes = { + @ProcessorTest(baseDir = "protobufTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) - void protobufBuilderTest() { + void protobufTest() { } @ProcessorTest(baseDir = "sealedSubclassTest") diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java deleted file mode 100644 index ae3740848f..0000000000 --- a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.itest.protobuf; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for generation of Protobuf Builder Mapper implementations - * - * @author Christian Bandowski - */ -public class ProtobufMapperTest { - - @Test - public void testSimpleImmutableBuilderHappyPath() { - PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder() - .setAge( 33 ) - .setName( "Bob" ) - .setAddress( PersonProtos.Person.Address.newBuilder() - .setAddressLine( "Wild Drive" ) - .build() ) - .build() ); - - assertThat( personDto.getAge() ).isEqualTo( 33 ); - assertThat( personDto.getName() ).isEqualTo( "Bob" ); - assertThat( personDto.getAddress() ).isNotNull(); - assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); - } - - @Test - public void testLombokToImmutable() { - PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); - - assertThat( person.getAge() ).isEqualTo( 33 ); - assertThat( person.getName() ).isEqualTo( "Bob" ); - assertThat( person.getAddress() ).isNotNull(); - assertThat( person.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); - } -} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml b/integrationtest/src/test/resources/protobufTest/pom.xml similarity index 84% rename from integrationtest/src/test/resources/protobufBuilderTest/pom.xml rename to integrationtest/src/test/resources/protobufTest/pom.xml index c0d8e2a81b..1a160f2ca1 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml +++ b/integrationtest/src/test/resources/protobufTest/pom.xml @@ -24,13 +24,18 @@ 1.6.0 0.6.1 + + 3.24.2 com.google.protobuf protobuf-java - provided + + + com.google.api.grpc + proto-google-common-protos @@ -55,11 +60,9 @@ compile-custom - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} - + io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java similarity index 100% rename from integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java rename to integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java new file mode 100644 index 0000000000..8d3edf6fef --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java @@ -0,0 +1,413 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.protobuf; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.Month; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.FloatValue; +import com.google.protobuf.StringValue; +import org.mapstruct.itest.protobuf.EverythingModel; + +public class Everything { + // Primitive types + private Integer int32; + private long int64; + private Float float_; + private Double double_; + private Boolean bool; + private String string; + private ByteString bytes; + + // Wrapper types + private Integer int32Value; + private long int64Value; + private FloatValue floatValue; + private double doubleValue; + private boolean boolValue; + private StringValue stringValue; + private ByteString bytesValue; + + // repeated + private Integer[] repeatedInt32; + private Set repeatedInt64; + private List repeatedFloat; + private List repeatedDouble; + private List repeatedBool; + private List repeatedString; + private List repeatedBytes; + + // map + private Map mapInt32String; + private Map mapInt64String; + private Map mapBoolString; + private Map mapStringString; + private Map mapStringBytes; + + // message + private EverythingModel.Message message; + private List repeatedMessage; + private Map mapStringMessage; + + // enum + private EverythingModel.Enum enum_; + private int optionalEnum; + private List repeatedEnum; + private Map mapStringEnum; + + // wellknown + private Instant timestamp; + private Duration duration; + + // google/type package + private LocalTime timeOfDay; + private String date; + private DayOfWeek dayOfWeek; + private Month month; + + public Integer getInt32() { + return this.int32; + } + + public long getInt64() { + return this.int64; + } + + public Float getFloat_() { + return this.float_; + } + + public Double getDouble_() { + return this.double_; + } + + public Boolean getBool() { + return this.bool; + } + + public String getString() { + return this.string; + } + + public ByteString getBytes() { + return this.bytes; + } + + public Integer getInt32Value() { + return this.int32Value; + } + + public long getInt64Value() { + return this.int64Value; + } + + public FloatValue getFloatValue() { + return this.floatValue; + } + + public double getDoubleValue() { + return this.doubleValue; + } + + public boolean isBoolValue() { + return this.boolValue; + } + + public StringValue getStringValue() { + return this.stringValue; + } + + public ByteString getBytesValue() { + return this.bytesValue; + } + + public Integer[] getRepeatedInt32() { + return this.repeatedInt32; + } + + public Set getRepeatedInt64() { + return this.repeatedInt64; + } + + public List getRepeatedFloat() { + return this.repeatedFloat; + } + + public List getRepeatedDouble() { + return this.repeatedDouble; + } + + public List getRepeatedBool() { + return this.repeatedBool; + } + + public List getRepeatedString() { + return this.repeatedString; + } + + public List getRepeatedBytes() { + return this.repeatedBytes; + } + + public Map getMapInt32String() { + return this.mapInt32String; + } + + public Map getMapInt64String() { + return this.mapInt64String; + } + + public Map getMapBoolString() { + return this.mapBoolString; + } + + public Map getMapStringString() { + return this.mapStringString; + } + + public Map getMapStringBytes() { + return this.mapStringBytes; + } + + public EverythingModel.Message getMessage() { + return this.message; + } + + public List getRepeatedMessage() { + return this.repeatedMessage; + } + + public Map getMapStringMessage() { + return this.mapStringMessage; + } + + public EverythingModel.Enum getEnum_() { + return this.enum_; + } + + public int getOptionalEnum() { + return this.optionalEnum; + } + + public List getRepeatedEnum() { + return this.repeatedEnum; + } + + public Map getMapStringEnum() { + return this.mapStringEnum; + } + + public Instant getTimestamp() { + return this.timestamp; + } + + public Duration getDuration() { + return this.duration; + } + + public LocalTime getTimeOfDay() { + return this.timeOfDay; + } + + public String getDate() { + return this.date; + } + + public DayOfWeek getDayOfWeek() { + return this.dayOfWeek; + } + + public Month getMonth() { + return this.month; + } + + public void setInt32(Integer int32) { + this.int32 = int32; + } + + public void setInt64(long int64) { + this.int64 = int64; + } + + public void setFloat_(Float float_) { + this.float_ = float_; + } + + public void setDouble_(Double double_) { + this.double_ = double_; + } + + public void setBool(Boolean bool) { + this.bool = bool; + } + + public void setString(String string) { + this.string = string; + } + + public void setBytes(ByteString bytes) { + this.bytes = bytes; + } + + public void setInt32Value(Integer int32Value) { + this.int32Value = int32Value; + } + + public void setInt64Value(long int64Value) { + this.int64Value = int64Value; + } + + public void setFloatValue(FloatValue floatValue) { + this.floatValue = floatValue; + } + + public void setDoubleValue(double doubleValue) { + this.doubleValue = doubleValue; + } + + public void setBoolValue(boolean boolValue) { + this.boolValue = boolValue; + } + + public void setStringValue(StringValue stringValue) { + this.stringValue = stringValue; + } + + public void setBytesValue(ByteString bytesValue) { + this.bytesValue = bytesValue; + } + + public void setRepeatedInt32(Integer[] repeatedInt32) { + this.repeatedInt32 = repeatedInt32; + } + + public void setRepeatedInt64(Set repeatedInt64) { + this.repeatedInt64 = repeatedInt64; + } + + public void setRepeatedFloat(List repeatedFloat) { + this.repeatedFloat = repeatedFloat; + } + + public void setRepeatedDouble(List repeatedDouble) { + this.repeatedDouble = repeatedDouble; + } + + public void setRepeatedBool(List repeatedBool) { + this.repeatedBool = repeatedBool; + } + + public void setRepeatedString(List repeatedString) { + this.repeatedString = repeatedString; + } + + public void setRepeatedBytes(List repeatedBytes) { + this.repeatedBytes = repeatedBytes; + } + + public void setMapInt32String(Map mapInt32String) { + this.mapInt32String = mapInt32String; + } + + public void setMapInt64String(Map mapInt64String) { + this.mapInt64String = mapInt64String; + } + + public void setMapBoolString(Map mapBoolString) { + this.mapBoolString = mapBoolString; + } + + public void setMapStringString(Map mapStringString) { + this.mapStringString = mapStringString; + } + + public void setMapStringBytes(Map mapStringBytes) { + this.mapStringBytes = mapStringBytes; + } + + public void setMessage(EverythingModel.Message message) { + this.message = message; + } + + public void setRepeatedMessage(List repeatedMessage) { + this.repeatedMessage = repeatedMessage; + } + + public void setMapStringMessage(Map mapStringMessage) { + this.mapStringMessage = mapStringMessage; + } + + public void setEnum_(EverythingModel.Enum enum_) { + this.enum_ = enum_; + } + + public void setOptionalEnum(int optionalEnum) { + this.optionalEnum = optionalEnum; + } + + public void setRepeatedEnum(List repeatedEnum) { + this.repeatedEnum = repeatedEnum; + } + + public void setMapStringEnum(Map mapStringEnum) { + this.mapStringEnum = mapStringEnum; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public void setTimeOfDay(LocalTime timeOfDay) { + this.timeOfDay = timeOfDay; + } + + public void setDate(String date) { + this.date = date; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public void setMonth(Month month) { + this.month = month; + } + + public static class Message { + private long id; + private String name; + + public long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public void setId(long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java new file mode 100644 index 0000000000..54e31d1757 --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.protobuf; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; +import static org.mapstruct.ReportingPolicy.ERROR; + +import org.mapstruct.itest.protobuf.EverythingModel; +import org.mapstruct.itest.protobuf.Everything; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = ALWAYS, unmappedTargetPolicy = ERROR) +public interface EverythingMapper { + + EverythingMapper INSTANCE = Mappers.getMapper(EverythingMapper.class); + + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + Everything modelToEntity(EverythingModel model); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + EverythingModel entityToModel(Everything entity); +} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java similarity index 100% rename from integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java rename to integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java similarity index 81% rename from integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java rename to integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java index b5cdc8575a..37f1624668 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java @@ -9,11 +9,12 @@ import org.mapstruct.ReportingPolicy; import org.mapstruct.factory.Mappers; -@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) // protobuf has a lot of strange additional setters/getters +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); PersonProtos.Person fromDto(PersonDto personDto); + PersonDto toDto(PersonProtos.Person personDto); } diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/proto/Person.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/Person.proto similarity index 100% rename from integrationtest/src/test/resources/protobufBuilderTest/src/main/proto/Person.proto rename to integrationtest/src/test/resources/protobufTest/src/main/proto/Person.proto diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto new file mode 100644 index 0000000000..3fbe47f433 --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto @@ -0,0 +1,82 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +syntax = "proto3"; + +package itest.protobuf; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/type/timeofday.proto"; +import "google/type/date.proto"; +import "google/type/dayofweek.proto"; +import "google/type/month.proto"; + +option java_package = "org.mapstruct.itest.protobuf"; +option java_multiple_files = true; +option java_outer_classname = "EverythingProto"; + +message EverythingModel { + int32 int32 = 1; + int64 int64 = 3; + float float = 11; + double double = 12; + bool bool = 13; + string string = 14; + bytes bytes = 15; + repeated int32 repeated_int32 = 16; + repeated int64 repeated_int64 = 18; + repeated float repeated_float = 26; + repeated double repeated_double = 27; + repeated bool repeated_bool = 28; + repeated string repeated_string = 29; + repeated bytes repeated_bytes = 30; + + google.protobuf.Int32Value int32_value = 20; + google.protobuf.Int64Value int64_value = 21; + google.protobuf.FloatValue float_value = 22; + google.protobuf.DoubleValue double_value = 23; + google.protobuf.BoolValue bool_value = 24; + google.protobuf.StringValue string_value = 25; + google.protobuf.BytesValue bytes_value = 31; + + map map_int32_string = 32; + map map_int64_string = 34; + map map_bool_string = 44; + map map_string_string = 45; + + map map_string_bytes = 46; + + Message message = 50; + repeated Message repeated_message = 51; + map map_string_message = 52; + + Enum enum = 60; + optional Enum optional_enum = 62; + repeated Enum repeated_enum = 61; + map map_string_enum = 63; + + // wellknown + google.protobuf.Timestamp timestamp = 65; + google.protobuf.Duration duration = 66; + + // google/type package + google.type.TimeOfDay time_of_day = 70; + google.type.Date date = 71; + google.type.DayOfWeek day_of_week = 73; + google.type.Month month = 74; + + message Message { + int64 id = 1; + string name = 2; + } + + enum Enum { + ENUM_UNSPECIFIED = 0; + ENUM_VALUE_1 = 1; + ENUM_VALUE_2 = 2; + } +} diff --git a/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java new file mode 100644 index 0000000000..6f102cb68d --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -0,0 +1,178 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.protobuf; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.FloatValue; +import com.google.protobuf.StringValue; +import org.junit.Test; +import org.mapstruct.itest.protobuf.EverythingMapper; + +/** + * Test for generation of Protobuf Builder Mapper implementations + * + * @author Christian Bandowski + * @author Freeman + */ +public class ProtobufMapperTest { + + @Test + public void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder() + .setAge( 33 ) + .setName( "Bob" ) + .setAddress( PersonProtos.Person.Address.newBuilder() + .setAddressLine( "Wild Drive" ) + .build() ) + .build() ); + + assertThat( personDto.getAge() ).isEqualTo( 33 ); + assertThat( personDto.getName() ).isEqualTo( "Bob" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); + } + + @Test + public void testLombokToImmutable() { + PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); + + assertThat( person.getAge() ).isEqualTo( 33 ); + assertThat( person.getName() ).isEqualTo( "Bob" ); + assertThat( person.getAddress() ).isNotNull(); + assertThat( person.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); + } + + @Test + public void testProtobufConvert() { + Everything original = new Everything(); + + // Primitive types + original.setInt32( 42 ); + original.setInt64( 123456L ); + original.setFloat_( 3.14f ); + original.setDouble_( 2.718 ); + original.setBool( true ); + original.setString( "test string" ); + original.setBytes( ByteString.copyFromUtf8( "test bytes" ) ); + + // Wrapper types + original.setInt32Value( 100 ); + original.setInt64Value( 99999L ); + original.setFloatValue( FloatValue.of( 1.5f ) ); + original.setDoubleValue( 9.99 ); + original.setBoolValue( false ); + original.setStringValue( StringValue.of( "wrapped string" ) ); + original.setBytesValue( ByteString.copyFromUtf8( "wrapped bytes" ) ); + + // Repeated fields + original.setRepeatedInt32( new Integer[] { 1, 2, 3, 4, 5 } ); + Set int64Set = new HashSet<>(); + int64Set.add( 10L ); + int64Set.add( 20L ); + int64Set.add( 30L ); + original.setRepeatedInt64( int64Set ); + original.setRepeatedFloat( Arrays.asList( 1.1f, 2.2f, 3.3f ) ); + original.setRepeatedDouble( Arrays.asList( + DoubleValue.of( 4.4 ), + DoubleValue.of( 5.5 ), + DoubleValue.of( 6.6 ) + ) ); + original.setRepeatedBool( Arrays.asList( BoolValue.of( true ), BoolValue.of( false ), BoolValue.of( true ) ) ); + original.setRepeatedString( Arrays.asList( "first", "second", "third" ) ); + original.setRepeatedBytes( Arrays.asList( + BytesValue.of( ByteString.copyFromUtf8( "bytes1" ) ), + BytesValue.of( ByteString.copyFromUtf8( "bytes2" ) ) + ) ); + + // Map fields + Map mapInt32String = new HashMap<>(); + mapInt32String.put( 1, ByteString.copyFromUtf8( "one" ) ); + mapInt32String.put( 2, ByteString.copyFromUtf8( "two" ) ); + original.setMapInt32String( mapInt32String ); + + Map mapInt64String = new HashMap<>(); + mapInt64String.put( 100L, "hundred" ); + mapInt64String.put( 200L, "two hundred" ); + original.setMapInt64String( mapInt64String ); + + Map mapBoolString = new HashMap<>(); + mapBoolString.put( BoolValue.of( true ), "true value" ); + mapBoolString.put( BoolValue.of( false ), "false value" ); + original.setMapBoolString( mapBoolString ); + + Map mapStringString = new HashMap<>(); + mapStringString.put( "key1", "value1" ); + mapStringString.put( "key2", "value2" ); + original.setMapStringString( mapStringString ); + + Map mapStringBytes = new HashMap<>(); + mapStringBytes.put( "bytes_key", BytesValue.of( ByteString.copyFromUtf8( "bytes_value" ) ) ); + original.setMapStringBytes( mapStringBytes ); + + // Message fields + original.setMessage( EverythingModel.Message.newBuilder() + .setId( 999L ) + .setName( "test message" ) + .build() ); + + Everything.Message msg1 = new Everything.Message(); + msg1.setId( 1L ); + msg1.setName( "message 1" ); + Everything.Message msg2 = new Everything.Message(); + msg2.setId( 2L ); + msg2.setName( "message 2" ); + original.setRepeatedMessage( Arrays.asList( msg1, msg2 ) ); + + Map mapStringMessage = new HashMap<>(); + Everything.Message msg3 = new Everything.Message(); + msg3.setId( 3L ); + msg3.setName( "message 3" ); + mapStringMessage.put( StringValue.of( "msg_key" ), msg3 ); + original.setMapStringMessage( mapStringMessage ); + + // Enum fields + original.setEnum_( EverythingModel.Enum.ENUM_VALUE_1 ); + original.setOptionalEnum( EverythingModel.Enum.ENUM_VALUE_2.getNumber() ); + original.setRepeatedEnum( Arrays.asList( "ENUM_VALUE_1", "ENUM_VALUE_2" ) ); + + Map mapStringEnum = new HashMap<>(); + mapStringEnum.put( "enum1", EverythingModel.Enum.ENUM_VALUE_1.getNumber() ); + mapStringEnum.put( "enum2", EverythingModel.Enum.ENUM_VALUE_2.getNumber() ); + original.setMapStringEnum( mapStringEnum ); + + // Well-known types + original.setTimestamp( Instant.ofEpochSecond( 1234567890, 123456789 ) ); + original.setDuration( Duration.ofSeconds( 3600, 500000000 ) ); + + // Google type package + original.setTimeOfDay( LocalTime.of( 14, 30, 45, 123456789 ) ); + original.setDate( "2023-12-25" ); + original.setDayOfWeek( DayOfWeek.MONDAY ); + original.setMonth( Month.JANUARY ); + + // Convert to Protobuf and back, verify equality + EverythingModel model = EverythingMapper.INSTANCE.entityToModel( original ); + Everything result = EverythingMapper.INSTANCE.modelToEntity( model ); + + assertThat( result ).usingRecursiveComparison().isEqualTo( original ); + } +} diff --git a/parent/pom.xml b/parent/pom.xml index f90b066774..7301637019 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -252,6 +252,11 @@ protobuf-java ${protobuf.version} + + com.google.api.grpc + proto-google-common-protos + 2.48.0 + org.inferred freebuilder diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 5090c903f3..115a7c284a 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -29,6 +29,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.ProtobufConstants; import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; @@ -202,6 +203,8 @@ public Conversions(TypeFactory typeFactory) { register( Locale.class, String.class, new LocaleToStringConversion() ); registerURLConversion(); + + registerProtobufConversions(); } private void registerJodaConversions() { @@ -312,6 +315,58 @@ private void registerURLConversion() { } } + private void registerProtobufConversions() { + if ( typeFactory.isTypeAvailable( ProtobufConstants.BYTE_STRING_FQN ) ) { + register( ProtobufConstants.BYTE_STRING_FQN, String.class, new ProtobufByteStringConversion() ); + } + + // enum types, use ordinal() for protobuf enums is a mistake + if ( typeFactory.isTypeAvailable( ProtobufConstants.PROTOCOL_MESSAGE_ENUM_FQN ) ) { + register( ProtobufConstants.PROTOCOL_MESSAGE_ENUM_FQN, Integer.class, new ProtobufEnumConversion() ); + register( ProtobufConstants.PROTOCOL_MESSAGE_ENUM_FQN, int.class, new ProtobufEnumConversion() ); + } + + // wrapped types + if ( typeFactory.isTypeAvailable( ProtobufConstants.DOUBLE_VALUE_FQN ) ) { + register( ProtobufConstants.DOUBLE_VALUE_FQN, Double.class, new ProtobufDoubleValueConversion() ); + register( ProtobufConstants.DOUBLE_VALUE_FQN, double.class, new ProtobufDoubleValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.FLOAT_VALUE_FQN ) ) { + register( ProtobufConstants.FLOAT_VALUE_FQN, Float.class, new ProtobufFloatValueConversion() ); + register( ProtobufConstants.FLOAT_VALUE_FQN, float.class, new ProtobufFloatValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.INT32_VALUE_FQN ) ) { + register( ProtobufConstants.INT32_VALUE_FQN, Integer.class, new ProtobufInt32ValueConversion() ); + register( ProtobufConstants.INT32_VALUE_FQN, int.class, new ProtobufInt32ValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.UINT32_VALUE_FQN ) ) { + register( ProtobufConstants.UINT32_VALUE_FQN, Integer.class, new ProtobufUInt32ValueConversion() ); + register( ProtobufConstants.UINT32_VALUE_FQN, int.class, new ProtobufUInt32ValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.INT64_VALUE_FQN ) ) { + register( ProtobufConstants.INT64_VALUE_FQN, Long.class, new ProtobufInt64ValueConversion() ); + register( ProtobufConstants.INT64_VALUE_FQN, long.class, new ProtobufInt64ValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.UINT64_VALUE_FQN ) ) { + register( ProtobufConstants.UINT64_VALUE_FQN, Long.class, new ProtobufUInt64ValueConversion() ); + register( ProtobufConstants.UINT64_VALUE_FQN, long.class, new ProtobufUInt64ValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.BOOL_VALUE_FQN ) ) { + register( ProtobufConstants.BOOL_VALUE_FQN, Boolean.class, new ProtobufBoolValueConversion() ); + register( ProtobufConstants.BOOL_VALUE_FQN, boolean.class, new ProtobufBoolValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.STRING_VALUE_FQN ) ) { + register( ProtobufConstants.STRING_VALUE_FQN, String.class, new ProtobufStringValueConversion() ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.BYTES_VALUE_FQN ) ) { + register( + ProtobufConstants.BYTES_VALUE_FQN, + ProtobufConstants.BYTE_STRING_FQN, + new ProtobufBytesValueConversion() + ); + } + } + private boolean isJavaURLAvailable() { return typeFactory.isTypeAvailable( "java.net.URL" ); } @@ -332,23 +387,53 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro conversions.put( new Key( targetType, sourceType ), inverse( conversion ) ); } + private void register(String sourceTypeName, String targetTypeName, ConversionProvider conversion) { + Type sourceType = typeFactory.getType( sourceTypeName ); + Type targetType = typeFactory.getType( targetTypeName ); + + conversions.put( new Key( sourceType, targetType ), conversion ); + conversions.put( new Key( targetType, sourceType ), inverse( conversion ) ); + } + public ConversionProvider getConversion(Type sourceType, Type targetType) { if ( sourceType.isEnumType() && ( targetType.equals( stringType ) || targetType.getBoxedEquivalent().equals( integerType ) ) ) { - sourceType = enumType; + sourceType = getEnumType( sourceType, targetType ); } else if ( targetType.isEnumType() && ( sourceType.equals( stringType ) || sourceType.getBoxedEquivalent().equals( integerType ) ) ) { - targetType = enumType; + targetType = getEnumType( sourceType, targetType ); } return conversions.get( new Key( sourceType, targetType ) ); } + private Type getEnumType(Type sourceType, Type targetType) { + Type protobufEnumType = getProtobufEnumType(); + if ( protobufEnumType != null ) { + if ( sourceType.isAssignableTo( protobufEnumType ) + && targetType.getBoxedEquivalent().equals( integerType ) ) { + return protobufEnumType; + } + if ( targetType.isAssignableTo( protobufEnumType ) + && sourceType.getBoxedEquivalent().equals( integerType ) ) { + return protobufEnumType; + } + } + + return enumType; + } + + private Type getProtobufEnumType() { + return typeFactory.isTypeAvailable( ProtobufConstants.PROTOCOL_MESSAGE_ENUM_FQN ) + ? typeFactory.getType( ProtobufConstants.PROTOCOL_MESSAGE_ENUM_FQN ) + : null; + } + private static class Key { private final Type sourceType; private final Type targetType; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java new file mode 100644 index 0000000000..b2e14e6fcd --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.BoolValue} and {@link Boolean}. + * + * @author Freeman + */ +public class ProtobufBoolValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.BOOL_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java new file mode 100644 index 0000000000..d17919cc89 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.ByteString} and {@link String}. + * + * @author Freeman + */ +public class ProtobufByteStringConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toStringUtf8()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".copyFromUtf8( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.BYTE_STRING_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java new file mode 100644 index 0000000000..bec1fa1d8e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.BytesValue} and {@link com.google.protobuf.ByteString}. + * + * @author Freeman + */ +public class ProtobufBytesValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.BYTES_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java new file mode 100644 index 0000000000..3d46a5060b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.DoubleValue} and {@link Double}. + * + * @author Freeman + */ +public class ProtobufDoubleValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.DOUBLE_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java new file mode 100644 index 0000000000..8d0e0f6716 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link com.google.protobuf.ProtocolMessageEnum} and {@link Integer} types. + *

+ * Protobuf enums have {@code getNumber()} method to convert to int and static {@code forNumber(int)} + * method to convert from int. + * + * @author Freeman + */ +public class ProtobufEnumConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getNumber()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return String.format( + "%s.requireNonNull(%s.forNumber( ), \"Invalid enum number\")", + conversionContext.getTypeFactory().getType( Objects.class ).createReferenceName(), + conversionContext.getTargetType().createReferenceName() + ); + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return asSet( + conversionContext.getTargetType(), + conversionContext.getTypeFactory().getType( Objects.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java new file mode 100644 index 0000000000..748b23f3d2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.FloatValue} and {@link Float}. + * + * @author Freeman + */ +public class ProtobufFloatValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.FLOAT_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java new file mode 100644 index 0000000000..9af2f4e137 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.Int32Value} and {@link Integer}. + * + * @author Freeman + */ +public class ProtobufInt32ValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.INT32_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java new file mode 100644 index 0000000000..cacf5d3855 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.Int64Value} and {@link Long}. + * + * @author Freeman + */ +public class ProtobufInt64ValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.INT64_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java new file mode 100644 index 0000000000..277f97241b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.StringValue} and {@link String}. + * + * @author Freeman + */ +public class ProtobufStringValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.STRING_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java new file mode 100644 index 0000000000..dac213c6c0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.UInt32Value} and {@link Integer}. + * + * @author Freeman + */ +public class ProtobufUInt32ValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.UINT32_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java new file mode 100644 index 0000000000..5f8e8e3b21 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +/** + * Conversion between {@link com.google.protobuf.UInt64Value} and {@link Long}. + * + * @author Freeman + */ +public class ProtobufUInt64ValueConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".getValue()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ProtobufConstants.UINT64_VALUE_FQN ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java index 6cd1605b23..fde03edc48 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.util.JaxbConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.ProtobufConstants; import org.mapstruct.ap.internal.util.XmlConstants; /** @@ -63,6 +64,35 @@ public BuiltInMappingMethods(TypeFactory typeFactory) { builtInMethods.add( new JodaLocalTimeToXmlGregorianCalendar( typeFactory ) ); builtInMethods.add( new XmlGregorianCalendarToJodaLocalTime( typeFactory ) ); } + + registerProtobuf( typeFactory ); + } + + private void registerProtobuf(TypeFactory typeFactory) { + if ( typeFactory.isTypeAvailable( ProtobufConstants.TIMESTAMP_FQN ) ) { + builtInMethods.add( new ProtobufTimestampToInstant( typeFactory ) ); + builtInMethods.add( new InstantToProtobufTimestamp( typeFactory ) ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.DURATION_FQN ) ) { + builtInMethods.add( new ProtobufDurationToJavaDuration( typeFactory ) ); + builtInMethods.add( new JavaDurationToProtobufDuration( typeFactory ) ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.DATE_FQN ) ) { + builtInMethods.add( new ProtobufDateToLocalDate( typeFactory ) ); + builtInMethods.add( new LocalDateToProtobufDate( typeFactory ) ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.DAY_OF_WEEK_FQN ) ) { + builtInMethods.add( new ProtobufDayOfWeekToJavaDayOfWeek( typeFactory ) ); + builtInMethods.add( new JavaDayOfWeekToProtobufDayOfWeek( typeFactory ) ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.MONTH_FQN ) ) { + builtInMethods.add( new ProtobufMonthToJavaMonth( typeFactory ) ); + builtInMethods.add( new JavaMonthToProtobufMonth( typeFactory ) ); + } + if ( typeFactory.isTypeAvailable( ProtobufConstants.TIME_OF_DAY_FQN ) ) { + builtInMethods.add( new ProtobufTimeOfDayToLocalTime( typeFactory ) ); + builtInMethods.add( new LocalTimeToProtobufTimeOfDay( typeFactory ) ); + } } private static boolean isJavaxJaxbAvailable(TypeFactory typeFactory) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java new file mode 100644 index 0000000000..83ba5c9e79 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Instant; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link Instant} to {@link com.google.protobuf.Timestamp}. + * + * @author Freeman + */ +public class InstantToProtobufTimestamp extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public InstantToProtobufTimestamp(TypeFactory typeFactory) { + this.parameter = new Parameter( "instant", typeFactory.getType( Instant.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.TIMESTAMP_FQN ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java new file mode 100644 index 0000000000..27f8c337c0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.DayOfWeek; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link DayOfWeek} to {@link com.google.type.DayOfWeek}. + * + * @author Freeman + */ +public class JavaDayOfWeekToProtobufDayOfWeek extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public JavaDayOfWeekToProtobufDayOfWeek(TypeFactory typeFactory) { + this.parameter = new Parameter( "dayOfWeek", typeFactory.getType( DayOfWeek.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.DAY_OF_WEEK_FQN ); + this.importTypes = asSet( returnType, parameter.getType(), typeFactory.getType( Objects.class ) ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java new file mode 100644 index 0000000000..725ff969fb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Duration; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link Duration} to {@link com.google.protobuf.Duration}. + * + * @author Freeman + */ +public class JavaDurationToProtobufDuration extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public JavaDurationToProtobufDuration(TypeFactory typeFactory) { + this.parameter = new Parameter( "duration", typeFactory.getType( Duration.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.DURATION_FQN ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java new file mode 100644 index 0000000000..cb8ecb42b2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Month; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link Month} to {@link com.google.type.Month}. + * + * @author Freeman + */ +public class JavaMonthToProtobufMonth extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public JavaMonthToProtobufMonth(TypeFactory typeFactory) { + this.parameter = new Parameter( "month", typeFactory.getType( Month.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.MONTH_FQN ); + this.importTypes = asSet( returnType, parameter.getType(), typeFactory.getType( Objects.class ) ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java new file mode 100644 index 0000000000..a2022de045 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.LocalDate; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link LocalDate} to {@link com.google.type.Date}. + * + * @author Freeman + */ +public class LocalDateToProtobufDate extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public LocalDateToProtobufDate(TypeFactory typeFactory) { + this.parameter = new Parameter( "date", typeFactory.getType( LocalDate.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.DATE_FQN ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java new file mode 100644 index 0000000000..0428d84c1b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.LocalTime; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link LocalTime} to {@link com.google.type.TimeOfDay}. + * + * @author Freeman + */ +public class LocalTimeToProtobufTimeOfDay extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public LocalTimeToProtobufTimeOfDay(TypeFactory typeFactory) { + this.parameter = new Parameter( "timeOfDay", typeFactory.getType( LocalTime.class ) ); + this.returnType = typeFactory.getType( ProtobufConstants.TIME_OF_DAY_FQN ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java new file mode 100644 index 0000000000..5843788213 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.LocalDate; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.type.Date} to {@link LocalDate}. + * + * @author Freeman + */ +public class ProtobufDateToLocalDate extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufDateToLocalDate(TypeFactory typeFactory) { + this.parameter = new Parameter( "date", typeFactory.getType( ProtobufConstants.DATE_FQN ) ); + this.returnType = typeFactory.getType( LocalDate.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java new file mode 100644 index 0000000000..50ee05d2c6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.DayOfWeek; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.type.DayOfWeek} to {@link DayOfWeek}. + * + * @author Freeman + */ +public class ProtobufDayOfWeekToJavaDayOfWeek extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufDayOfWeekToJavaDayOfWeek(TypeFactory typeFactory) { + this.parameter = new Parameter( "dayOfWeek", typeFactory.getType( ProtobufConstants.DAY_OF_WEEK_FQN ) ); + this.returnType = typeFactory.getType( DayOfWeek.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java new file mode 100644 index 0000000000..eb9588471f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Duration; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.protobuf.Duration} to {@link Duration}. + * + * @author Freeman + */ +public class ProtobufDurationToJavaDuration extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufDurationToJavaDuration(TypeFactory typeFactory) { + this.parameter = new Parameter( "duration", typeFactory.getType( ProtobufConstants.DURATION_FQN ) ); + this.returnType = typeFactory.getType( Duration.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java new file mode 100644 index 0000000000..b8c5206222 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Month; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.type.Month} to {@link Month}. + * + * @author Freeman + */ +public class ProtobufMonthToJavaMonth extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufMonthToJavaMonth(TypeFactory typeFactory) { + this.parameter = new Parameter( "month", typeFactory.getType( ProtobufConstants.MONTH_FQN ) ); + this.returnType = typeFactory.getType( Month.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java new file mode 100644 index 0000000000..a9170812f9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.LocalTime; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.type.TimeOfDay} to {@link LocalTime}. + * + * @author Freeman + */ +public class ProtobufTimeOfDayToLocalTime extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufTimeOfDayToLocalTime(TypeFactory typeFactory) { + this.parameter = new Parameter( "timeOfDay", typeFactory.getType( ProtobufConstants.TIME_OF_DAY_FQN ) ); + this.returnType = typeFactory.getType( LocalTime.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java new file mode 100644 index 0000000000..884ea69a42 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.builtin; + +import java.time.Instant; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ProtobufConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Converts {@link com.google.protobuf.Timestamp} to {@link Instant}. + * + * @author Freeman + */ +public class ProtobufTimestampToInstant extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public ProtobufTimestampToInstant(TypeFactory typeFactory) { + this.parameter = new Parameter( "timestamp", typeFactory.getType( ProtobufConstants.TIMESTAMP_FQN ) ); + this.returnType = typeFactory.getType( Instant.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java index 81640848d0..86c3b73583 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -32,6 +32,7 @@ import org.mapstruct.ap.spi.ImmutablesBuilderProvider; import org.mapstruct.ap.spi.MapStructProcessingEnvironment; import org.mapstruct.ap.spi.NoOpBuilderProvider; +import org.mapstruct.ap.spi.ProtobufAccessorNamingStrategy; /** * Keeps contextual data in the scope of the entire annotation processor ("application scope"). @@ -97,6 +98,13 @@ else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) ! messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Freebuilder found on classpath" ); } } + else if ( elementUtils.getTypeElement( ProtobufConstants.MESSAGE_LITE_OR_BUILDER_FQN ) != null ) { + defaultAccessorNamingStrategy = new ProtobufAccessorNamingStrategy(); + defaultBuilderProvider = new DefaultBuilderProvider(); + if ( verbose ) { + messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Protobuf found on classpath" ); + } + } else { defaultAccessorNamingStrategy = new DefaultAccessorNamingStrategy(); defaultBuilderProvider = new DefaultBuilderProvider(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ProtobufConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ProtobufConstants.java new file mode 100644 index 0000000000..67ee73c310 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ProtobufConstants.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util; + +/** + * Helper for holding Protobuf FQN. + * + * @author Freeman + */ +public class ProtobufConstants { + + public static final String MESSAGE_LITE_OR_BUILDER_FQN = "com.google.protobuf.MessageLiteOrBuilder"; + public static final String PROTOCOL_MESSAGE_ENUM_FQN = "com.google.protobuf.ProtocolMessageEnum"; + + public static final String BYTE_STRING_FQN = "com.google.protobuf.ByteString"; + + // Well known types + public static final String TIMESTAMP_FQN = "com.google.protobuf.Timestamp"; + public static final String DURATION_FQN = "com.google.protobuf.Duration"; + + // Wrapper types + public static final String DOUBLE_VALUE_FQN = "com.google.protobuf.DoubleValue"; + public static final String FLOAT_VALUE_FQN = "com.google.protobuf.FloatValue"; + public static final String INT64_VALUE_FQN = "com.google.protobuf.Int64Value"; + public static final String UINT64_VALUE_FQN = "com.google.protobuf.UInt64Value"; + public static final String INT32_VALUE_FQN = "com.google.protobuf.Int32Value"; + public static final String UINT32_VALUE_FQN = "com.google.protobuf.UInt32Value"; + public static final String BOOL_VALUE_FQN = "com.google.protobuf.BoolValue"; + public static final String STRING_VALUE_FQN = "com.google.protobuf.StringValue"; + public static final String BYTES_VALUE_FQN = "com.google.protobuf.BytesValue"; + + // proto-google-common-protos google/type package + public static final String DATE_FQN = "com.google.type.Date"; + public static final String DAY_OF_WEEK_FQN = "com.google.type.DayOfWeek"; + public static final String MONTH_FQN = "com.google.type.Month"; + public static final String TIME_OF_DAY_FQN = "com.google.type.TimeOfDay"; + + private ProtobufConstants() { + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java new file mode 100644 index 0000000000..cca1ab25ba --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -0,0 +1,708 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.spi; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.util.IntrospectorUtils; + +/** + * An {@link AccessorNamingStrategy} implementation specifically designed for Protocol Buffers (Protobuf) + * generated classes. + * + *

+ * This strategy extends {@link DefaultAccessorNamingStrategy} to handle the unique accessor patterns + * generated by the Protocol Buffers compiler (protoc). Protobuf-generated classes have several + * characteristics that differ from standard JavaBeans: + *

+ * + *
    + *
  • Additional auxiliary methods for various field types (e.g., {@code getXxxBytes()}, {@code getXxxCount()}, {@code getXxxOrBuilder()})
  • + *
  • Special handling for repeated fields using {@code getXxxList()} and {@code addAllXxx()} methods
  • + *
  • Special handling for map fields using {@code getXxxMap()} and {@code putAllXxx()} methods
  • + *
  • Enumeration fields with corresponding {@code getXxxValue()} methods for integer representations
  • + *
  • String fields with {@code getXxxBytes()} methods for ByteString representations
  • + *
  • Message fields with {@code getXxxBuilder()} and {@code getXxxOrBuilder()} methods
  • + *
+ * + *

+ * This strategy filters out Protobuf-internal methods and auxiliary methods to expose only the primary + * accessors for each field, ensuring correct mapping behavior when using MapStruct with Protobuf types. + *

+ * + *

+ * For non-Protobuf types, this strategy delegates to the default behavior of {@link DefaultAccessorNamingStrategy}. + *

+ * + * @author Freeman + * @since 1.7.0 + */ +public class ProtobufAccessorNamingStrategy extends DefaultAccessorNamingStrategy { + /** + * repeated string getter will return ProtocolStringList + */ + private static final String PROTOCOL_STRING_LIST = "com.google.protobuf.ProtocolStringList"; + private static final String MESSAGE_LITE_OR_BUILDER = "com.google.protobuf.MessageLiteOrBuilder"; + private static final String MESSAGE_LITE = "com.google.protobuf.MessageLite"; + private static final String MESSAGE_LITE_BUILDER = "com.google.protobuf.MessageLite.Builder"; + private static final String MESSAGE = "com.google.protobuf.Message"; + private static final String MESSAGE_BUILDER = "com.google.protobuf.Message.Builder"; + private static final String PROTOCOL_MESSAGE_ENUM = "com.google.protobuf.ProtocolMessageEnum"; + + private final List specialGetterRules = getSpecialGetterRules(); + private final List specialSetterRules = getSpecialSetterRules(); + + /** + * interface name -> set of internal method signatures + */ + private Map> internalMethods; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + super.init( processingEnvironment ); + + internalMethods = getInternalMethods(); + } + + @Override + public boolean isGetterMethod(ExecutableElement method) { + if ( !isProtobufMessageOrBuilder( method.getEnclosingElement() ) ) { + return super.isGetterMethod( method ); + } + + return hasPrefixWithUpperCaseNext( method, "get" ) + && method.getParameters().isEmpty() + && !isInternalMethod( method ) + && !isDeprecated( method ) + && !isSpecialGetMethod( method ); + } + + @Override + public boolean isSetterMethod(ExecutableElement method) { + if ( !isProtobufMessageOrBuilder( method.getEnclosingElement() ) ) { + return super.isSetterMethod( method ); + } + + // Protobuf message only has fluent setters + return isFluentSetter( method ); + } + + @Override + protected boolean isFluentSetter(ExecutableElement method) { + if ( !isProtobufMessageOrBuilder( method.getEnclosingElement() ) ) { + return super.isFluentSetter( method ); + } + + // Only builder has setters + if ( !isProtobufMessageBuilder( method.getEnclosingElement() ) ) { + return false; + } + + if ( isAddAllMethod( method ) || isPutAllMethod( method ) ) { + return true; + } + + return hasPrefixWithUpperCaseNext( method, "set" ) + && method.getParameters().size() == 1 + && !isInternalMethod( method ) + && !isSpecialSetMethod( method ); + } + + @Override + public boolean isAdderMethod(ExecutableElement method) { + if ( !isProtobufMessageOrBuilder( method.getEnclosingElement() ) ) { + return super.isAdderMethod( method ); + } + + // Protobuf message do have adders for repeated and putters for map fields, + // but we uniformly use addAllXxx() and putAllXxx() methods as setters. + // Just make life easier. + return false; + } + + @Override + public boolean isPresenceCheckMethod(ExecutableElement method) { + return super.isPresenceCheckMethod( method ); + } + + @Override + public String getElementName(ExecutableElement adderMethod) { + return super.getElementName( adderMethod ); + } + + @Override + public String getPropertyName(ExecutableElement getterOrSetterMethod) { + if ( !isProtobufMessageOrBuilder( getterOrSetterMethod.getEnclosingElement() ) ) { + return super.getPropertyName( getterOrSetterMethod ); + } + + String methodName = getterOrSetterMethod.getSimpleName().toString(); + + // 'get...Map' + if ( isGetMap( getterOrSetterMethod ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 3, methodName.length() - 3 ) ); + } + + // 'get...List' + if ( isGetList( getterOrSetterMethod ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 3, methodName.length() - 4 ) ); + } + + // 'addAll...' + if ( isAddAllMethod( getterOrSetterMethod ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 6 ) ); + } + + // 'putAll...' + if ( isPutAllMethod( getterOrSetterMethod ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 6 ) ); + } + + return super.getPropertyName( getterOrSetterMethod ); + } + + private Map> getInternalMethods() { + String[] internalClasses = { + MESSAGE, + MESSAGE_BUILDER, + MESSAGE_LITE, + MESSAGE_LITE_BUILDER, + }; + + Map> methodsMap = new java.util.HashMap<>(); + for ( String className : internalClasses ) { + TypeElement typeElement = elementUtils.getTypeElement( className ); + if ( typeElement != null ) { + HashSet methods = new HashSet<>(); + collectMethods( typeElement, methods ); + methodsMap.put( className, methods ); + } + } + return methodsMap; + } + + private List getSpecialGetterRules() { + List rules = new ArrayList<>(); + + // string field generates extra getXxxBytes() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Bytes" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Bytes".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isTargetClass( m.getReturnType(), String.class ) + ); + } + ) ); + + // repeated and map field generates extra getXxxCount() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Count" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); + // map field generates getXxxMap() getter (getXxx() is deprecated) + boolean hasMapGetter = methods.stream() + .anyMatch( m -> ( getMethodName( m ).equals( withoutSuffix + "Map" ) || + getMethodName( m ).equals( withoutSuffix ) ) + && isMapType( m.getReturnType() ) + ); + if ( hasMapGetter ) { + return true; + } + // repeated field generates getXxxList() getter + return methods.stream() + .anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) + ); + } + ) ); + + // message field generates extra getXxxBuilder() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Builder" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Builder".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isMessageType( m.getReturnType() ) + ); + } + ) ); + + // message field generates extra getXxxOrBuilder() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "OrBuilder" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "OrBuilder".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isMessageType( m.getReturnType() ) + ); + } + ) ); + + // repeated message field generates extra getXxxBuilderList() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "BuilderList" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "BuilderList".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) + ); + } + ) ); + + // repeated message field generates extra getXxxOrBuilderList() method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "OrBuilderList" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "OrBuilderList".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) + ); + } + ) ); + + // enum field generates extra 'int getXxxValue()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Value" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isProtobufEnumType( m.getReturnType() ) + ); + } + ) ); + + // repeated enum field generates extra 'List getXxxValueList()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "ValueList" ) + && isListType( method.getReturnType() ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "ValueList".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) + ); + } + ) ); + + // map field generates extra 'Map getXxxValueMap()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "ValueMap" ) + && isMapType( method.getReturnType() ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "ValueMap".length() ); + return methods.stream().anyMatch( m -> + ( getMethodName( m ).equals( withoutSuffix + "Map" ) || getMethodName( m ).equals( withoutSuffix ) ) + && isMapType( m.getReturnType() ) + ); + } + ) ); + + return rules; + } + + private List getSpecialSetterRules() { + List rules = new ArrayList<>(); + + // string field generates extra setXxxBytes() method + rules.add( new SpecialMethodRule( + method -> hasPrefixWithUpperCaseNext( method, "set" ) + && getMethodName( method ).endsWith( "Bytes" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Bytes".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isFirstParameterTargetClass( m, String.class ) + ); + } + ) ); + + // enum field generates extra 'setXxxValue(int value)' method + rules.add( new SpecialMethodRule( + method -> hasPrefixWithUpperCaseNext( method, "set" ) + && getMethodName( method ).endsWith( "Value" ) + && isFirstParameterTargetClass( method, int.class ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && m.getParameters().size() == 1 + && isProtobufEnumType( m.getParameters().get( 0 ).asType() + ) ); + } + ) ); + + // repeated enum field generates extra 'addAllXxxValue(Iterable values)' method + // addAllXxx(Iterable values) + rules.add( new SpecialMethodRule( + method -> hasPrefixWithUpperCaseNext( method, "addAll" ) + && getMethodName( method ).endsWith( "Value" ) + && isFirstParameterTargetClass( method, Iterable.class ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isFirstParameterTargetClass( m, Iterable.class ) + ); + } + ) ); + + // map field generates extra 'putAllXxxValue(Map values)' method + // putAllXxx(Map values) + rules.add( new SpecialMethodRule( + method -> hasPrefixWithUpperCaseNext( method, "putAll" ) + && getMethodName( method ).endsWith( "Value" ) + && isFirstParameterTargetClass( method, Map.class ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix ) + && isFirstParameterTargetClass( m, Map.class ) + ); + } + ) ); + + // message field generates extra setXxx(Message.Builder builder) method + rules.add( new SpecialMethodRule( + method -> hasPrefixWithUpperCaseNext( method, "set" ) + && isFirstParameterTargetSubType( method, MESSAGE_LITE_BUILDER ), + (method, methods) -> { + String methodName = getMethodName( method ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( methodName ) + && isFirstParameterTargetSubType( m, MESSAGE_LITE ) + ); + } + ) ); + + return rules; + } + + private static boolean isFirstParameterTargetClass(ExecutableElement method, String canonicalName) { + return method.getParameters().size() == 1 + && isTargetClass( method.getParameters().get( 0 ).asType(), canonicalName ); + } + + private boolean isFirstParameterTargetSubType(ExecutableElement method, String canonicalName) { + return method.getParameters().size() == 1 + && isTargetSubType( method.getParameters().get( 0 ).asType(), canonicalName ); + } + + private static boolean isFirstParameterTargetClass(ExecutableElement method, Class targetClass) { + return isFirstParameterTargetClass( method, targetClass.getCanonicalName() ); + } + + private boolean isSpecialGetMethod(ExecutableElement method) { + List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); + for ( SpecialMethodRule rule : specialGetterRules ) { + if ( rule.matches( method, allMethods ) ) { + return true; + } + } + return false; + } + + private boolean isSpecialSetMethod(ExecutableElement method) { + List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); + for ( SpecialMethodRule rule : specialSetterRules ) { + if ( rule.matches( method, allMethods ) ) { + return true; + } + } + return false; + } + + private boolean isAddAllMethod(ExecutableElement method) { + return hasPrefixWithUpperCaseNext( method, "addAll" ) + && isFirstParameterTargetClass( method, Iterable.class ) + && !isSpecialSetMethod( method ); + } + + private boolean isPutAllMethod(ExecutableElement method) { + return hasPrefixWithUpperCaseNext( method, "putAll" ) + && isFirstParameterTargetClass( method, Map.class ) + && !isSpecialSetMethod( method ); + } + + private static boolean isTargetClass(TypeMirror paramType, Class targetType) { + return isTargetClass( paramType, targetType.getCanonicalName() ); + } + + private static boolean isTargetClass(TypeMirror paramType, String canonicalName) { + return paramType.toString().startsWith( canonicalName ); + } + + private boolean isProtobufEnumType(TypeMirror t) { + if ( t.getKind() != TypeKind.DECLARED ) { + return false; + } + + TypeElement protobufEnumElement = elementUtils.getTypeElement( PROTOCOL_MESSAGE_ENUM ); + if ( protobufEnumElement == null ) { + return false; + } + + TypeMirror protobufEnumType = protobufEnumElement.asType(); + return typeUtils.isSubtype( t, protobufEnumType ); + } + + private boolean isGetList(ExecutableElement element) { + // repeated fields getter: getXxxList() + return hasPrefixWithUpperCaseNext( element, "get" ) + && isListType( element.getReturnType() ); + } + + private boolean isGetMap(ExecutableElement element) { + // There are many getter methods for map in protobuf generated code, only one is the real getter: + // - getXxx deprecated + // - getMutableXxx deprecated + // - getXxxMap the real getter + return hasPrefixWithUpperCaseNext( element, "get" ) + && isMapType( element.getReturnType() ) + && !isDeprecated( element ); + } + + private static String getMethodName(ExecutableElement element) { + return element.getSimpleName().toString(); + } + + private static boolean isDeprecated(ExecutableElement element) { + return element.getAnnotation( Deprecated.class ) != null; + } + + private static boolean isListType(TypeMirror t) { + return isTargetClass( t, List.class ) || t.toString().startsWith( PROTOCOL_STRING_LIST ); + } + + private static boolean isMapType(TypeMirror t) { + return isTargetClass( t, Map.class ); + } + + private boolean isProtobufMessageOrBuilder(Element type) { + if ( !( type instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) type; + return isMessageOrBuilderType( typeElement.asType() ); + } + + private boolean isProtobufMessageBuilder(Element type) { + if ( !( type instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) type; + return isMessageBuilderType( typeElement.asType() ); + } + + private boolean isMessageOrBuilderType(TypeMirror t) { + return isTargetSubType( t, MESSAGE_LITE_OR_BUILDER ); + } + + private boolean isMessageBuilderType(TypeMirror t) { + return isTargetSubType( t, MESSAGE_LITE_BUILDER ); + } + + private boolean isMessageType(TypeMirror t) { + return isTargetSubType( t, MESSAGE_LITE ); + } + + private boolean isTargetSubType(TypeMirror t, String canonicalName) { + if ( t.getKind() != TypeKind.DECLARED ) { + return false; + } + + TypeElement messageType = elementUtils.getTypeElement( canonicalName ); + if ( messageType == null ) { + return false; + } + + return typeUtils.isSubtype( t, messageType.asType() ); + } + + private boolean isInternalMethod(ExecutableElement method) { + Element enclosingElement = method.getEnclosingElement(); + if ( !( enclosingElement instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) enclosingElement; + MethodSignature signature = new MethodSignature( method ); + + // Check all interfaces and superclasses of the enclosing type + for ( Map.Entry> entry : internalMethods.entrySet() ) { + String interfaceName = entry.getKey(); + Set internalMethods = entry.getValue(); + + TypeElement interfaceElement = elementUtils.getTypeElement( interfaceName ); + if ( interfaceElement != null && typeUtils.isSubtype( typeElement.asType(), interfaceElement.asType() ) ) { + if ( internalMethods.contains( signature ) ) { + return true; + } + } + } + + return false; + } + + private static void collectMethods(TypeElement typeElement, HashSet methods) { + // Collect methods from current type + for ( Element element : typeElement.getEnclosedElements() ) { + if ( element instanceof ExecutableElement && isPublicNonStaticMethod( (ExecutableElement) element ) ) { + methods.add( new MethodSignature( (ExecutableElement) element ) ); + } + } + + // Collect from superclass + TypeMirror superclass = typeElement.getSuperclass(); + if ( superclass instanceof DeclaredType ) { + DeclaredType declaredType = (DeclaredType) superclass; + Element superElement = declaredType.asElement(); + if ( superElement instanceof TypeElement && !superElement.toString().equals( "java.lang.Object" ) ) { + collectMethods( (TypeElement) superElement, methods ); + } + } + + // Collect from interfaces + for ( TypeMirror interfaceType : typeElement.getInterfaces() ) { + if ( interfaceType instanceof DeclaredType ) { + DeclaredType declaredType = (DeclaredType) interfaceType; + Element interfaceElement = declaredType.asElement(); + if ( interfaceElement instanceof TypeElement ) { + collectMethods( (TypeElement) interfaceElement, methods ); + } + } + } + } + + private static List getPublicNonStaticMethods(Element type) { + return type + .getEnclosedElements() + .stream() + .filter( e -> { + if ( e instanceof ExecutableElement ) { + return isPublicNonStaticMethod( (ExecutableElement) e ); + } + else { + return false; + } + } ) + .map( e -> (ExecutableElement) e ) + .collect( Collectors.toList() ); + } + + private static boolean isPublicNonStaticMethod(ExecutableElement method) { + Set modifiers = method.getModifiers(); + return method.getKind() == ElementKind.METHOD + && modifiers.contains( Modifier.PUBLIC ) + && !modifiers.contains( Modifier.STATIC ); + } + + private static boolean hasPrefixWithUpperCaseNext(ExecutableElement method, String prefix) { + String name = method.getSimpleName().toString(); + int len = prefix.length(); + return name.startsWith( prefix ) + && name.length() > len + && Character.isUpperCase( name.charAt( len ) ); + } + + /** + * Represents a rule for detecting special Protobuf-generated methods. + * + *

+ * Special methods are auxiliary methods generated by protoc (like getXxxBytes(), getXxxCount(), etc.) + * that should not be treated as normal getters/setters. + * + *

+ * The rule has two stages: + * 1. Quick check: Fast preliminary check based on method signature (e.g., method name ends with "Bytes") + * 2. Full check: Complete verification by checking if corresponding primary method exists + */ + private static final class SpecialMethodRule { + private final Predicate quickCheck; + private final BiPredicate> fullCheck; + + public SpecialMethodRule( + Predicate quickCheck, + BiPredicate> fullCheck) { + this.quickCheck = quickCheck; + this.fullCheck = fullCheck; + } + + public boolean matches(ExecutableElement method, List allMethods) { + return quickCheck.test( method ) && fullCheck.test( method, allMethods ); + } + } + + private static final class MethodSignature { + private final String name; + private final List parameterTypes; + + public MethodSignature(ExecutableElement method) { + this.name = method.getSimpleName().toString(); + this.parameterTypes = method.getParameters().stream() + .map( p -> p.asType().toString() ) + .collect( Collectors.toList() ); + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MethodSignature that = (MethodSignature) o; + return Objects.equals( name, that.name ) && Objects.equals( parameterTypes, that.parameterTypes ); + } + + @Override + public int hashCode() { + return Objects.hash( name, parameterTypes ); + } + + @Override + public String toString() { + return name + "(" + String.join( ", ", parameterTypes ) + ")"; + } + } + +} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.ftl new file mode 100644 index 0000000000..fe9032c872 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.ftl @@ -0,0 +1,18 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.protobuf.Timestamp")/> ${name}( <@includeModel object=findType("java.time.Instant")/> instant ) { + if ( instant == null ) { + return null; + } + + <@includeModel object=findType("com.google.protobuf.Timestamp")/>.Builder builder = <@includeModel object=findType("com.google.protobuf.Timestamp")/>.newBuilder(); + builder.setSeconds( instant.getEpochSecond() ); + builder.setNanos( instant.getNano() ); + return builder.build(); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.ftl new file mode 100644 index 0000000000..97f40f1471 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.type.DayOfWeek")/> ${name}( <@includeModel object=findType("java.time.DayOfWeek")/> dayOfWeek ) { + if ( dayOfWeek == null ) { + return <@includeModel object=findType("com.google.type.DayOfWeek")/>.DAY_OF_WEEK_UNSPECIFIED; + } + + return <@includeModel object=findType("java.util.Objects")/>.requireNonNull( <@includeModel object=findType("com.google.type.DayOfWeek")/>.forNumber( dayOfWeek.getValue() ) ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.ftl new file mode 100644 index 0000000000..30a9afaf8f --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.ftl @@ -0,0 +1,18 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.protobuf.Duration")/> ${name}( <@includeModel object=findType("java.time.Duration")/> duration ) { + if ( duration == null ) { + return null; + } + + <@includeModel object=findType("com.google.protobuf.Duration")/>.Builder builder = <@includeModel object=findType("com.google.protobuf.Duration")/>.newBuilder(); + builder.setSeconds( duration.getSeconds() ); + builder.setNanos( duration.getNano() ); + return builder.build(); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.ftl new file mode 100644 index 0000000000..17b6e87501 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.type.Month")/> ${name}( <@includeModel object=findType("java.time.Month")/> month ) { + if ( month == null ) { + return <@includeModel object=findType("com.google.type.Month")/>.MONTH_UNSPECIFIED; + } + + return <@includeModel object=findType("java.util.Objects")/>.requireNonNull( <@includeModel object=findType("com.google.type.Month")/>.forNumber( month.getValue() ) ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.ftl new file mode 100644 index 0000000000..7117980248 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.ftl @@ -0,0 +1,19 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.type.Date")/> ${name}( <@includeModel object=findType("java.time.LocalDate")/> date ) { + if ( date == null ) { + return null; + } + + <@includeModel object=findType("com.google.type.Date")/>.Builder builder = <@includeModel object=findType("com.google.type.Date")/>.newBuilder(); + builder.setYear( date.getYear() ); + builder.setMonth( date.getMonthValue() ); + builder.setDay( date.getDayOfMonth() ); + return builder.build(); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.ftl new file mode 100644 index 0000000000..4dcc96b1b0 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.ftl @@ -0,0 +1,20 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("com.google.type.TimeOfDay")/> ${name}( <@includeModel object=findType("java.time.LocalTime")/> timeOfDay ) { + if ( timeOfDay == null ) { + return null; + } + + <@includeModel object=findType("com.google.type.TimeOfDay")/>.Builder builder = <@includeModel object=findType("com.google.type.TimeOfDay")/>.newBuilder(); + builder.setHours( timeOfDay.getHour() ); + builder.setMinutes( timeOfDay.getMinute() ); + builder.setSeconds( timeOfDay.getSecond() ); + builder.setNanos( timeOfDay.getNano() ); + return builder.build(); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.ftl new file mode 100644 index 0000000000..a51546cc0d --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.LocalDate")/> ${name}( <@includeModel object=findType("com.google.type.Date")/> date ) { + if ( date == null ) { + return null; + } + + return <@includeModel object=findType("java.time.LocalDate")/>.of( date.getYear(), date.getMonth(), date.getDay() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.ftl new file mode 100644 index 0000000000..f8d99f1034 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.DayOfWeek")/> ${name}( <@includeModel object=findType("com.google.type.DayOfWeek")/> dayOfWeek ) { + if ( dayOfWeek == null || dayOfWeek == <@includeModel object=findType("com.google.type.DayOfWeek")/>.DAY_OF_WEEK_UNSPECIFIED ) { + return null; + } + + return <@includeModel object=findType("java.time.DayOfWeek")/>.of( dayOfWeek.getNumber() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.ftl new file mode 100644 index 0000000000..4ef14873a7 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.Duration")/> ${name}( <@includeModel object=findType("com.google.protobuf.Duration")/> duration ) { + if ( duration == null ) { + return null; + } + + return <@includeModel object=findType("java.time.Duration")/>.ofSeconds( duration.getSeconds(), duration.getNanos() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.ftl new file mode 100644 index 0000000000..fdc910d083 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.Month")/> ${name}( <@includeModel object=findType("com.google.type.Month")/> month ) { + if ( month == null || month == <@includeModel object=findType("com.google.type.Month")/>.MONTH_UNSPECIFIED ) { + return null; + } + + return <@includeModel object=findType("java.time.Month")/>.of( month.getNumber() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.ftl new file mode 100644 index 0000000000..d718bc99ae --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.ftl @@ -0,0 +1,20 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.LocalTime")/> ${name}( <@includeModel object=findType("com.google.type.TimeOfDay")/> timeOfDay ) { + if ( timeOfDay == null ) { + return null; + } + + return <@includeModel object=findType("java.time.LocalTime")/>.of( + timeOfDay.getHours(), + timeOfDay.getMinutes(), + timeOfDay.getSeconds(), + timeOfDay.getNanos() + ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.ftl new file mode 100644 index 0000000000..89b5355a46 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.Instant")/> ${name}( <@includeModel object=findType("com.google.protobuf.Timestamp")/> timestamp ) { + if ( timestamp == null ) { + return null; + } + + return <@includeModel object=findType("java.time.Instant")/>.ofEpochSecond( timestamp.getSeconds(), timestamp.getNanos() ); +} From a6ad574d5e07f997ec0d1a24f662307eb379d702 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Mon, 10 Nov 2025 22:26:05 +0800 Subject: [PATCH 02/10] support oneof --- .../mapstruct/itest/protobuf/Everything.java | 38 +++++++++++++++++++ .../itest/protobuf/EverythingMapper.java | 14 +++---- .../src/main/proto/everything.proto | 8 ++++ .../spi/ProtobufAccessorNamingStrategy.java | 19 +++++++++- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java index 8d3edf6fef..bbff7fa152 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java @@ -78,6 +78,12 @@ public class Everything { private DayOfWeek dayOfWeek; private Month month; + // oneof + private int oneofInt32; + private String oneofString; + private Integer oneofEnum; + private Message oneofMessage; + public Integer getInt32() { return this.int32; } @@ -390,6 +396,38 @@ public void setMonth(Month month) { this.month = month; } + public int getOneofInt32() { + return oneofInt32; + } + + public void setOneofInt32(int oneofInt32) { + this.oneofInt32 = oneofInt32; + } + + public String getOneofString() { + return oneofString; + } + + public void setOneofString(String oneofString) { + this.oneofString = oneofString; + } + + public Integer getOneofEnum() { + return oneofEnum; + } + + public void setOneofEnum(Integer oneofEnum) { + this.oneofEnum = oneofEnum; + } + + public Message getOneofMessage() { + return oneofMessage; + } + + public void setOneofMessage(Message oneofMessage) { + this.oneofMessage = oneofMessage; + } + public static class Message { private long id; private String name; diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java index 54e31d1757..080b356675 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java @@ -5,19 +5,19 @@ */ package org.mapstruct.itest.protobuf; -import static org.mapstruct.NullValueCheckStrategy.ALWAYS; -import static org.mapstruct.ReportingPolicy.ERROR; - -import org.mapstruct.itest.protobuf.EverythingModel; -import org.mapstruct.itest.protobuf.Everything; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import org.mapstruct.itest.protobuf.Everything; +import org.mapstruct.itest.protobuf.EverythingModel; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; +import static org.mapstruct.ReportingPolicy.ERROR; -@Mapper(nullValueCheckStrategy = ALWAYS, unmappedTargetPolicy = ERROR) +@Mapper(nullValueCheckStrategy = ALWAYS, unmappedTargetPolicy = ERROR, unmappedSourcePolicy = ERROR) public interface EverythingMapper { - EverythingMapper INSTANCE = Mappers.getMapper(EverythingMapper.class); + EverythingMapper INSTANCE = Mappers.getMapper( EverythingMapper.class ); @Mapping(target = "float_", source = "float") @Mapping(target = "double_", source = "double") diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto index 3fbe47f433..407f9d1e84 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto @@ -69,6 +69,14 @@ message EverythingModel { google.type.DayOfWeek day_of_week = 73; google.type.Month month = 74; + // oneof field + oneof oneof { + int32 oneof_int32 = 80; + string oneof_string = 81; + Enum oneof_enum = 82; + Message oneof_message = 83; + } + message Message { int64 id = 1; string name = 2; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java index cca1ab25ba..3c1f9d5d06 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.spi; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -188,7 +189,7 @@ private Map> getInternalMethods() { MESSAGE_LITE_BUILDER, }; - Map> methodsMap = new java.util.HashMap<>(); + Map> methodsMap = new HashMap<>(); for ( String className : internalClasses ) { TypeElement typeElement = elementUtils.getTypeElement( className ); if ( typeElement != null ) { @@ -333,6 +334,14 @@ && isMapType( m.getReturnType() ) } ) ); + // oneof field generates extra 'getXxxCase()' + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Case" ) + && isEnumType( method.getReturnType() ) + && !isProtobufEnumType( method.getReturnType() ), + (method, methods) -> true + ) ); + return rules; } @@ -485,6 +494,14 @@ private boolean isProtobufEnumType(TypeMirror t) { return typeUtils.isSubtype( t, protobufEnumType ); } + private boolean isEnumType(TypeMirror t) { + if ( t.getKind() != TypeKind.DECLARED ) { + return false; + } + Element element = ( (DeclaredType) t ).asElement(); + return element.getKind() == ElementKind.ENUM; + } + private boolean isGetList(ExecutableElement element) { // repeated fields getter: getXxxList() return hasPrefixWithUpperCaseNext( element, "get" ) From 44b0d5b64e11313af7f9936105b15cb5d24643c1 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Tue, 11 Nov 2025 00:10:05 +0800 Subject: [PATCH 03/10] add test for proto2 and edition 2023 --- .../src/test/resources/protobufTest/pom.xml | 1 - .../mapstruct/itest/protobuf/Everything.java | 13 ++- .../itest/protobuf/EverythingMapper.java | 28 +++++- .../main/proto/everything_edition2023.proto | 85 +++++++++++++++++++ .../src/main/proto/everything_proto2.proto | 85 +++++++++++++++++++ ...erything.proto => everything_proto3.proto} | 9 +- .../itest/protobuf/ProtobufMapperTest.java | 36 +++++--- parent/pom.xml | 4 +- 8 files changed, 229 insertions(+), 32 deletions(-) create mode 100644 integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto create mode 100644 integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto rename integrationtest/src/test/resources/protobufTest/src/main/proto/{everything.proto => everything_proto3.proto} (90%) diff --git a/integrationtest/src/test/resources/protobufTest/pom.xml b/integrationtest/src/test/resources/protobufTest/pom.xml index 1a160f2ca1..d87886a2bc 100644 --- a/integrationtest/src/test/resources/protobufTest/pom.xml +++ b/integrationtest/src/test/resources/protobufTest/pom.xml @@ -62,7 +62,6 @@ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java index bbff7fa152..3b35707644 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java @@ -20,7 +20,6 @@ import com.google.protobuf.DoubleValue; import com.google.protobuf.FloatValue; import com.google.protobuf.StringValue; -import org.mapstruct.itest.protobuf.EverythingModel; public class Everything { // Primitive types @@ -58,12 +57,12 @@ public class Everything { private Map mapStringBytes; // message - private EverythingModel.Message message; + private Message message; private List repeatedMessage; private Map mapStringMessage; // enum - private EverythingModel.Enum enum_; + private Integer enum_; private int optionalEnum; private List repeatedEnum; private Map mapStringEnum; @@ -188,7 +187,7 @@ public Map getMapStringBytes() { return this.mapStringBytes; } - public EverythingModel.Message getMessage() { + public Message getMessage() { return this.message; } @@ -200,7 +199,7 @@ public Map getMapStringMessage() { return this.mapStringMessage; } - public EverythingModel.Enum getEnum_() { + public Integer getEnum_() { return this.enum_; } @@ -344,7 +343,7 @@ public void setMapStringBytes(Map mapStringBytes) { this.mapStringBytes = mapStringBytes; } - public void setMessage(EverythingModel.Message message) { + public void setMessage(Message message) { this.message = message; } @@ -356,7 +355,7 @@ public void setMapStringMessage(Map mapStringMessage) { this.mapStringMessage = mapStringMessage; } - public void setEnum_(EverythingModel.Enum enum_) { + public void setEnum_(Integer enum_) { this.enum_ = enum_; } diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java index 080b356675..e79fd4c878 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java @@ -9,7 +9,6 @@ import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import org.mapstruct.itest.protobuf.Everything; -import org.mapstruct.itest.protobuf.EverythingModel; import static org.mapstruct.NullValueCheckStrategy.ALWAYS; import static org.mapstruct.ReportingPolicy.ERROR; @@ -19,13 +18,36 @@ public interface EverythingMapper { EverythingMapper INSTANCE = Mappers.getMapper( EverythingMapper.class ); + // proto2 @Mapping(target = "float_", source = "float") @Mapping(target = "double_", source = "double") @Mapping(target = "enum_", source = "enum") - Everything modelToEntity(EverythingModel model); + Everything proto2ToJavaBean(EverythingProto2 proto); @Mapping(target = "float", source = "float_") @Mapping(target = "double", source = "double_") @Mapping(target = "enum", source = "enum_") - EverythingModel entityToModel(Everything entity); + EverythingProto2 javaBeanToProto2(Everything javaBean); + + // proto3 + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + Everything proto3ToJavaBean(EverythingProto3 proto); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + EverythingProto3 javaBeanToProto3(Everything javaBean); + + // edition 2023 + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + Everything edition2023ToJavaBean(EverythingEdition2023 proto); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + EverythingEdition2023 javaBeanToEdition2023(Everything javaBean); } diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto new file mode 100644 index 0000000000..99706ea231 --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto @@ -0,0 +1,85 @@ +edition = "2023"; + +package itest.protobuf; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/type/timeofday.proto"; +import "google/type/date.proto"; +import "google/type/dayofweek.proto"; +import "google/type/month.proto"; + +option java_package = "org.mapstruct.itest.protobuf"; +option java_multiple_files = true; +option java_outer_classname = "EverythingEdition2023Proto"; + +message EverythingEdition2023 { + int32 int32 = 1; + int64 int64 = 3; + float float = 11; + double double = 12; + bool bool = 13; + string string = 14; + bytes bytes = 15; + repeated int32 repeated_int32 = 16; + repeated int64 repeated_int64 = 18; + repeated float repeated_float = 26; + repeated double repeated_double = 27; + repeated bool repeated_bool = 28; + repeated string repeated_string = 29; + repeated bytes repeated_bytes = 30; + + google.protobuf.Int32Value int32_value = 20; + google.protobuf.Int64Value int64_value = 21; + google.protobuf.FloatValue float_value = 22; + google.protobuf.DoubleValue double_value = 23; + google.protobuf.BoolValue bool_value = 24; + google.protobuf.StringValue string_value = 25; + google.protobuf.BytesValue bytes_value = 31; + + map map_int32_string = 32; + map map_int64_string = 34; + map map_bool_string = 44; + map map_string_string = 45; + + map map_string_bytes = 46; + + Message message = 50; + repeated Message repeated_message = 51; + map map_string_message = 52; + + Enum enum = 60; + Enum optional_enum = 62 [features.field_presence = IMPLICIT]; + repeated Enum repeated_enum = 61; + map map_string_enum = 63; + + // wellknown + google.protobuf.Timestamp timestamp = 65; + google.protobuf.Duration duration = 66; + + // google/type package + google.type.TimeOfDay time_of_day = 70; + google.type.Date date = 71; + google.type.DayOfWeek day_of_week = 73; + google.type.Month month = 74; + + // oneof field + oneof oneof { + int32 oneof_int32 = 80; + string oneof_string = 81; + Enum oneof_enum = 82; + Message oneof_message = 83; + } + + message Message { + int64 id = 1; + string name = 2; + } + + enum Enum { + ENUM_UNSPECIFIED = 0; + ENUM_VALUE_1 = 1; + ENUM_VALUE_2 = 2; + } +} diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto new file mode 100644 index 0000000000..0d67921d84 --- /dev/null +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto @@ -0,0 +1,85 @@ +syntax = "proto2"; + +package itest.protobuf; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/type/timeofday.proto"; +import "google/type/date.proto"; +import "google/type/dayofweek.proto"; +import "google/type/month.proto"; + +option java_package = "org.mapstruct.itest.protobuf"; +option java_multiple_files = true; +option java_outer_classname = "EverythingProto2Proto"; + +message EverythingProto2 { + required int32 int32 = 1; + required int64 int64 = 3; + required float float = 11; + required double double = 12; + required bool bool = 13; + required string string = 14; + required bytes bytes = 15; + repeated int32 repeated_int32 = 16; + repeated int64 repeated_int64 = 18; + repeated float repeated_float = 26; + repeated double repeated_double = 27; + repeated bool repeated_bool = 28; + repeated string repeated_string = 29; + repeated bytes repeated_bytes = 30; + + required google.protobuf.Int32Value int32_value = 20; + required google.protobuf.Int64Value int64_value = 21; + required google.protobuf.FloatValue float_value = 22; + required google.protobuf.DoubleValue double_value = 23; + required google.protobuf.BoolValue bool_value = 24; + required google.protobuf.StringValue string_value = 25; + required google.protobuf.BytesValue bytes_value = 31; + + map map_int32_string = 32; + map map_int64_string = 34; + map map_bool_string = 44; + map map_string_string = 45; + + map map_string_bytes = 46; + + required Message message = 50; + repeated Message repeated_message = 51; + map map_string_message = 52; + + required Enum enum = 60; + optional Enum optional_enum = 62; + repeated Enum repeated_enum = 61; + map map_string_enum = 63; + + // wellknown + required google.protobuf.Timestamp timestamp = 65; + required google.protobuf.Duration duration = 66; + + // google/type package + required google.type.TimeOfDay time_of_day = 70; + required google.type.Date date = 71; + required google.type.DayOfWeek day_of_week = 73; + required google.type.Month month = 74; + + // oneof field + oneof oneof { + int32 oneof_int32 = 80; + string oneof_string = 81; + Enum oneof_enum = 82; + Message oneof_message = 83; + } + + message Message { + optional int64 id = 1; + optional string name = 2; + } + + enum Enum { + ENUM_UNSPECIFIED = 0; + ENUM_VALUE_1 = 1; + ENUM_VALUE_2 = 2; + } +} diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto similarity index 90% rename from integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto rename to integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto index 407f9d1e84..2b66d40e40 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto @@ -1,8 +1,3 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ syntax = "proto3"; package itest.protobuf; @@ -17,9 +12,9 @@ import "google/type/month.proto"; option java_package = "org.mapstruct.itest.protobuf"; option java_multiple_files = true; -option java_outer_classname = "EverythingProto"; +option java_outer_classname = "EverythingProto3Proto"; -message EverythingModel { +message EverythingProto3 { int32 int32 = 1; int64 int64 = 3; float float = 11; diff --git a/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java index 6f102cb68d..c88736d7f4 100644 --- a/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java +++ b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -129,10 +129,10 @@ public void testProtobufConvert() { original.setMapStringBytes( mapStringBytes ); // Message fields - original.setMessage( EverythingModel.Message.newBuilder() - .setId( 999L ) - .setName( "test message" ) - .build() ); + Everything.Message message = new Everything.Message(); + message.setId( 999L ); + message.setName( "test message" ); + original.setMessage( message ); Everything.Message msg1 = new Everything.Message(); msg1.setId( 1L ); @@ -150,13 +150,13 @@ public void testProtobufConvert() { original.setMapStringMessage( mapStringMessage ); // Enum fields - original.setEnum_( EverythingModel.Enum.ENUM_VALUE_1 ); - original.setOptionalEnum( EverythingModel.Enum.ENUM_VALUE_2.getNumber() ); + original.setEnum_( 1 ); + original.setOptionalEnum( 2 ); original.setRepeatedEnum( Arrays.asList( "ENUM_VALUE_1", "ENUM_VALUE_2" ) ); Map mapStringEnum = new HashMap<>(); - mapStringEnum.put( "enum1", EverythingModel.Enum.ENUM_VALUE_1.getNumber() ); - mapStringEnum.put( "enum2", EverythingModel.Enum.ENUM_VALUE_2.getNumber() ); + mapStringEnum.put( "enum1", 1 ); + mapStringEnum.put( "enum2", 2 ); original.setMapStringEnum( mapStringEnum ); // Well-known types @@ -169,10 +169,22 @@ public void testProtobufConvert() { original.setDayOfWeek( DayOfWeek.MONDAY ); original.setMonth( Month.JANUARY ); - // Convert to Protobuf and back, verify equality - EverythingModel model = EverythingMapper.INSTANCE.entityToModel( original ); - Everything result = EverythingMapper.INSTANCE.modelToEntity( model ); + // Oneof + original.setOneofInt32( 12345 ); + + // Convert + EverythingProto2 proto2Message = EverythingMapper.INSTANCE.javaBeanToProto2( original ); + Everything proto2Result = EverythingMapper.INSTANCE.proto2ToJavaBean( proto2Message ); + + EverythingProto3 proto3Message = EverythingMapper.INSTANCE.javaBeanToProto3( original ); + Everything proto3Result = EverythingMapper.INSTANCE.proto3ToJavaBean( proto3Message ); + + EverythingEdition2023 edition2023Message = EverythingMapper.INSTANCE.javaBeanToEdition2023( original ); + Everything edition2023Result = EverythingMapper.INSTANCE.edition2023ToJavaBean( edition2023Message ); - assertThat( result ).usingRecursiveComparison().isEqualTo( original ); + // Use AssertJ recursive comparison to verify equality + assertThat( proto3Result ).usingRecursiveComparison().isEqualTo( original ); + assertThat( proto2Result ).usingRecursiveComparison().isEqualTo( original ); + assertThat( edition2023Result ).usingRecursiveComparison().isEqualTo( original ); } } diff --git a/parent/pom.xml b/parent/pom.xml index 7301637019..5d178eb010 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -50,7 +50,7 @@ The processor module needs at least Java 21. --> 1.8 - 3.25.5 + 4.33.0 2.3.2 @@ -255,7 +255,7 @@ com.google.api.grpc proto-google-common-protos - 2.48.0 + 2.63.1 org.inferred From 681251796c6ae97bf9fd8e4878e666c1a34ed36c Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Tue, 11 Nov 2025 00:12:18 +0800 Subject: [PATCH 04/10] add license header --- .../protobufTest/src/main/proto/everything_edition2023.proto | 5 +++++ .../protobufTest/src/main/proto/everything_proto2.proto | 5 +++++ .../protobufTest/src/main/proto/everything_proto3.proto | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto index 99706ea231..4a0b3dd19d 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ edition = "2023"; package itest.protobuf; diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto index 0d67921d84..57b62e4a3e 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ syntax = "proto2"; package itest.protobuf; diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto index 2b66d40e40..56745e923e 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ syntax = "proto3"; package itest.protobuf; From 6ae402e05ac6d6df0ebb3cf9a8eb47239eb3bff0 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Tue, 11 Nov 2025 01:03:41 +0800 Subject: [PATCH 05/10] fixes lint --- .../ap/internal/conversion/ProtobufBoolValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufByteStringConversion.java | 2 +- .../ap/internal/conversion/ProtobufBytesValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufDoubleValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufEnumConversion.java | 2 +- .../ap/internal/conversion/ProtobufFloatValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufInt32ValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufInt64ValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufStringValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufUInt32ValueConversion.java | 2 +- .../ap/internal/conversion/ProtobufUInt64ValueConversion.java | 2 +- .../model/source/builtin/InstantToProtobufTimestamp.java | 2 +- .../model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java | 2 +- .../model/source/builtin/JavaDurationToProtobufDuration.java | 2 +- .../internal/model/source/builtin/JavaMonthToProtobufMonth.java | 2 +- .../internal/model/source/builtin/LocalDateToProtobufDate.java | 2 +- .../model/source/builtin/LocalTimeToProtobufTimeOfDay.java | 2 +- .../internal/model/source/builtin/ProtobufDateToLocalDate.java | 2 +- .../model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java | 2 +- .../model/source/builtin/ProtobufDurationToJavaDuration.java | 2 +- .../internal/model/source/builtin/ProtobufMonthToJavaMonth.java | 2 +- .../model/source/builtin/ProtobufTimeOfDayToLocalTime.java | 2 +- .../model/source/builtin/ProtobufTimestampToInstant.java | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java index b2e14e6fcd..f0fab313e5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBoolValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.BoolValue} and {@link Boolean}. + * Conversion between {@code com.google.protobuf.BoolValue} and {@link Boolean}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java index d17919cc89..d34329ebf9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufByteStringConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.ByteString} and {@link String}. + * Conversion between {@code com.google.protobuf.ByteString} and {@link String}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java index bec1fa1d8e..7704584d80 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufBytesValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.BytesValue} and {@link com.google.protobuf.ByteString}. + * Conversion between {@code com.google.protobuf.BytesValue} and {@code com.google.protobuf.ByteString}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java index 3d46a5060b..c586ac70bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufDoubleValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.DoubleValue} and {@link Double}. + * Conversion between {@code com.google.protobuf.DoubleValue} and {@link Double}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java index 8d0e0f6716..5369d853d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufEnumConversion.java @@ -14,7 +14,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Conversion between {@link com.google.protobuf.ProtocolMessageEnum} and {@link Integer} types. + * Conversion between {@code com.google.protobuf.ProtocolMessageEnum} and {@link Integer} types. *

* Protobuf enums have {@code getNumber()} method to convert to int and static {@code forNumber(int)} * method to convert from int. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java index 748b23f3d2..463911093b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufFloatValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.FloatValue} and {@link Float}. + * Conversion between {@code com.google.protobuf.FloatValue} and {@link Float}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java index 9af2f4e137..544af0f666 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt32ValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.Int32Value} and {@link Integer}. + * Conversion between {@code com.google.protobuf.Int32Value} and {@link Integer}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java index cacf5d3855..35c3de8210 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufInt64ValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.Int64Value} and {@link Long}. + * Conversion between {@code com.google.protobuf.Int64Value} and {@link Long}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java index 277f97241b..91aa6ccfdc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufStringValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.StringValue} and {@link String}. + * Conversion between {@code com.google.protobuf.StringValue} and {@link String}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java index dac213c6c0..6371d7f35b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt32ValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.UInt32Value} and {@link Integer}. + * Conversion between {@code com.google.protobuf.UInt32Value} and {@link Integer}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java index 5f8e8e3b21..8fd7b52c6f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ProtobufUInt64ValueConversion.java @@ -13,7 +13,7 @@ import org.mapstruct.ap.internal.util.ProtobufConstants; /** - * Conversion between {@link com.google.protobuf.UInt64Value} and {@link Long}. + * Conversion between {@code com.google.protobuf.UInt64Value} and {@link Long}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java index 83ba5c9e79..0fcb6224da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/InstantToProtobufTimestamp.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link Instant} to {@link com.google.protobuf.Timestamp}. + * Converts {@link Instant} to {@code com.google.protobuf.Timestamp}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java index 27f8c337c0..c21229972b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDayOfWeekToProtobufDayOfWeek.java @@ -17,7 +17,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link DayOfWeek} to {@link com.google.type.DayOfWeek}. + * Converts {@link DayOfWeek} to {@code com.google.type.DayOfWeek}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java index 725ff969fb..30af43bf7e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaDurationToProtobufDuration.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link Duration} to {@link com.google.protobuf.Duration}. + * Converts {@link Duration} to {@code com.google.protobuf.Duration}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java index cb8ecb42b2..8303a6a297 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JavaMonthToProtobufMonth.java @@ -17,7 +17,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link Month} to {@link com.google.type.Month}. + * Converts {@link Month} to {@code com.google.type.Month}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java index a2022de045..06c6b3e547 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToProtobufDate.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link LocalDate} to {@link com.google.type.Date}. + * Converts {@link LocalDate} to {@code com.google.type.Date}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java index 0428d84c1b..d8fbc5206c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalTimeToProtobufTimeOfDay.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link LocalTime} to {@link com.google.type.TimeOfDay}. + * Converts {@link LocalTime} to {@code com.google.type.TimeOfDay}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java index 5843788213..6ab620c980 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDateToLocalDate.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.type.Date} to {@link LocalDate}. + * Converts {@code com.google.type.Date} to {@link LocalDate}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java index 50ee05d2c6..68b8c0105d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDayOfWeekToJavaDayOfWeek.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.type.DayOfWeek} to {@link DayOfWeek}. + * Converts {@code com.google.type.DayOfWeek} to {@link DayOfWeek}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java index eb9588471f..e19b750686 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufDurationToJavaDuration.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.protobuf.Duration} to {@link Duration}. + * Converts {@code com.google.protobuf.Duration} to {@link Duration}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java index b8c5206222..535cb3b069 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufMonthToJavaMonth.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.type.Month} to {@link Month}. + * Converts {@code com.google.type.Month} to {@link Month}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java index a9170812f9..945f89bc84 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimeOfDayToLocalTime.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.type.TimeOfDay} to {@link LocalTime}. + * Converts {@code com.google.type.TimeOfDay} to {@link LocalTime}. * * @author Freeman */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java index 884ea69a42..4dd1c392de 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ProtobufTimestampToInstant.java @@ -16,7 +16,7 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** - * Converts {@link com.google.protobuf.Timestamp} to {@link Instant}. + * Converts {@code com.google.protobuf.Timestamp} to {@link Instant}. * * @author Freeman */ From aa82e7c55668ec2d7bd2aa0c7672613b03998b9f Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Thu, 13 Nov 2025 00:27:30 +0800 Subject: [PATCH 06/10] should not depends on Deprecated annotation --- .../spi/ProtobufAccessorNamingStrategy.java | 182 ++++++++++++++---- 1 file changed, 140 insertions(+), 42 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java index 3c1f9d5d06..fcd74159df 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -93,8 +93,7 @@ public boolean isGetterMethod(ExecutableElement method) { return hasPrefixWithUpperCaseNext( method, "get" ) && method.getParameters().isEmpty() && !isInternalMethod( method ) - && !isDeprecated( method ) - && !isSpecialGetMethod( method ); + && !isSpecialGetterMethod( method ); } @Override @@ -125,7 +124,7 @@ protected boolean isFluentSetter(ExecutableElement method) { return hasPrefixWithUpperCaseNext( method, "set" ) && method.getParameters().size() == 1 && !isInternalMethod( method ) - && !isSpecialSetMethod( method ); + && !isSpecialSetterMethod( method ); } @Override @@ -159,12 +158,12 @@ public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); // 'get...Map' - if ( isGetMap( getterOrSetterMethod ) ) { + if ( isMapGetter( getterOrSetterMethod ) ) { return IntrospectorUtils.decapitalize( methodName.substring( 3, methodName.length() - 3 ) ); } // 'get...List' - if ( isGetList( getterOrSetterMethod ) ) { + if ( isRepeatedGetter( getterOrSetterMethod ) ) { return IntrospectorUtils.decapitalize( methodName.substring( 3, methodName.length() - 4 ) ); } @@ -203,6 +202,17 @@ private Map> getInternalMethods() { private List getSpecialGetterRules() { List rules = new ArrayList<>(); + rules.addAll( stringGetterSpecialMethodRules() ); + rules.addAll( messageGetterGetterMethodRules() ); + rules.addAll( enumGetterSpecialMethodRules() ); + rules.addAll( repeatedGetterSpecialMethodRules() ); + rules.addAll( mapGetterSpecialMethodRules() ); + rules.addAll( oneofGetterSpecialMethodRules() ); + return rules; + } + + private List stringGetterSpecialMethodRules() { + List rules = new ArrayList<>(); // string field generates extra getXxxBytes() method rules.add( new SpecialMethodRule( @@ -217,29 +227,11 @@ && isTargetClass( m.getReturnType(), String.class ) } ) ); - // repeated and map field generates extra getXxxCount() method - rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "Count" ), - (method, methods) -> { - String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); - // map field generates getXxxMap() getter (getXxx() is deprecated) - boolean hasMapGetter = methods.stream() - .anyMatch( m -> ( getMethodName( m ).equals( withoutSuffix + "Map" ) || - getMethodName( m ).equals( withoutSuffix ) ) - && isMapType( m.getReturnType() ) - ); - if ( hasMapGetter ) { - return true; - } - // repeated field generates getXxxList() getter - return methods.stream() - .anyMatch( m -> - getMethodName( m ).equals( withoutSuffix + "List" ) - && isListType( m.getReturnType() ) - ); - } - ) ); + return rules; + } + + private List messageGetterGetterMethodRules() { + List rules = new ArrayList<>(); // message field generates extra getXxxBuilder() method rules.add( new SpecialMethodRule( @@ -293,6 +285,12 @@ && isListType( m.getReturnType() ) } ) ); + return rules; + } + + private List enumGetterSpecialMethodRules() { + List rules = new ArrayList<>(); + // enum field generates extra 'int getXxxValue()' method rules.add( new SpecialMethodRule( method -> getMethodName( method ).endsWith( "Value" ), @@ -334,6 +332,74 @@ && isMapType( m.getReturnType() ) } ) ); + return rules; + } + + private List repeatedGetterSpecialMethodRules() { + List rules = new ArrayList<>(); + + // repeated field generates 'getXxxCount()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Count" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) + ); + } + ) ); + + return rules; + } + + private List mapGetterSpecialMethodRules() { + List rules = new ArrayList<>(); + + // map field generates 'getXxxCount()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "Count" ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( withoutSuffix + "Map" ) + && isMapType( m.getReturnType() ) + ); + } + ) ); + + // map field generates deprecated 'getXxx()' and 'getMutableXxx()' methods + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).startsWith( "get" ) + && !getMethodName( method ).endsWith( "Map" ) + && isMapType( method.getReturnType() ), + (method, methods) -> { + String methodName = getMethodName( method ); + if ( methodName.startsWith( "getMutable" ) ) { + String withoutPrefix = methodName.substring( "getMutable".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( "get" + withoutPrefix + "Map" ) + && isMapType( m.getReturnType() ) + ); + } + else { + String withoutPrefix = methodName.substring( "get".length() ); + return methods.stream().anyMatch( m -> + getMethodName( m ).equals( "get" + withoutPrefix + "Map" ) + && isMapType( m.getReturnType() ) + ); + } + } + ) ); + + return rules; + } + + private List oneofGetterSpecialMethodRules() { + List rules = new ArrayList<>(); + // oneof field generates extra 'getXxxCase()' rules.add( new SpecialMethodRule( method -> getMethodName( method ).endsWith( "Case" ) @@ -347,6 +413,14 @@ && isEnumType( method.getReturnType() ) private List getSpecialSetterRules() { List rules = new ArrayList<>(); + rules.addAll( stringSetterSpecialMethodRules() ); + rules.addAll( enumSetterSpecialMethodRules() ); + rules.addAll( messageSetterSpecialMethodRules() ); + return rules; + } + + private List stringSetterSpecialMethodRules() { + List rules = new ArrayList<>(); // string field generates extra setXxxBytes() method rules.add( new SpecialMethodRule( @@ -362,7 +436,13 @@ && isFirstParameterTargetClass( m, String.class ) } ) ); - // enum field generates extra 'setXxxValue(int value)' method + return rules; + } + + private List enumSetterSpecialMethodRules() { + List rules = new ArrayList<>(); + + // enum field generates extra 'setXxxValue(int)' method rules.add( new SpecialMethodRule( method -> hasPrefixWithUpperCaseNext( method, "set" ) && getMethodName( method ).endsWith( "Value" ) @@ -410,6 +490,12 @@ && isFirstParameterTargetClass( m, Map.class ) } ) ); + return rules; + } + + private List messageSetterSpecialMethodRules() { + List rules = new ArrayList<>(); + // message field generates extra setXxx(Message.Builder builder) method rules.add( new SpecialMethodRule( method -> hasPrefixWithUpperCaseNext( method, "set" ) @@ -440,7 +526,7 @@ private static boolean isFirstParameterTargetClass(ExecutableElement method, Cla return isFirstParameterTargetClass( method, targetClass.getCanonicalName() ); } - private boolean isSpecialGetMethod(ExecutableElement method) { + private boolean isSpecialGetterMethod(ExecutableElement method) { List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); for ( SpecialMethodRule rule : specialGetterRules ) { if ( rule.matches( method, allMethods ) ) { @@ -450,7 +536,7 @@ private boolean isSpecialGetMethod(ExecutableElement method) { return false; } - private boolean isSpecialSetMethod(ExecutableElement method) { + private boolean isSpecialSetterMethod(ExecutableElement method) { List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); for ( SpecialMethodRule rule : specialSetterRules ) { if ( rule.matches( method, allMethods ) ) { @@ -463,13 +549,13 @@ private boolean isSpecialSetMethod(ExecutableElement method) { private boolean isAddAllMethod(ExecutableElement method) { return hasPrefixWithUpperCaseNext( method, "addAll" ) && isFirstParameterTargetClass( method, Iterable.class ) - && !isSpecialSetMethod( method ); + && !isSpecialSetterMethod( method ); } private boolean isPutAllMethod(ExecutableElement method) { return hasPrefixWithUpperCaseNext( method, "putAll" ) && isFirstParameterTargetClass( method, Map.class ) - && !isSpecialSetMethod( method ); + && !isSpecialSetterMethod( method ); } private static boolean isTargetClass(TypeMirror paramType, Class targetType) { @@ -502,30 +588,37 @@ private boolean isEnumType(TypeMirror t) { return element.getKind() == ElementKind.ENUM; } - private boolean isGetList(ExecutableElement element) { + private boolean isRepeatedGetter(ExecutableElement element) { // repeated fields getter: getXxxList() return hasPrefixWithUpperCaseNext( element, "get" ) + && getMethodName( element ).endsWith( "List" ) && isListType( element.getReturnType() ); } - private boolean isGetMap(ExecutableElement element) { + private boolean isMapGetter(ExecutableElement element) { // There are many getter methods for map in protobuf generated code, only one is the real getter: // - getXxx deprecated // - getMutableXxx deprecated // - getXxxMap the real getter - return hasPrefixWithUpperCaseNext( element, "get" ) - && isMapType( element.getReturnType() ) - && !isDeprecated( element ); + if ( hasPrefixWithUpperCaseNext( element, "get" ) + && getMethodName( element ).endsWith( "Map" ) + && isMapType( element.getReturnType() ) ) { + List allMethods = getPublicNonStaticMethods( element.getEnclosingElement() ); + for ( SpecialMethodRule rule : mapGetterSpecialMethodRules() ) { + if ( rule.matches( element, allMethods ) ) { + return false; + } + } + return true; + } + + return false; } private static String getMethodName(ExecutableElement element) { return element.getSimpleName().toString(); } - private static boolean isDeprecated(ExecutableElement element) { - return element.getAnnotation( Deprecated.class ) != null; - } - private static boolean isListType(TypeMirror t) { return isTargetClass( t, List.class ) || t.toString().startsWith( PROTOCOL_STRING_LIST ); } @@ -689,6 +782,11 @@ public SpecialMethodRule( public boolean matches(ExecutableElement method, List allMethods) { return quickCheck.test( method ) && fullCheck.test( method, allMethods ); } + + public boolean matches(ExecutableElement method) { + List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); + return quickCheck.test( method ) && fullCheck.test( method, allMethods ); + } } private static final class MethodSignature { From 67d047dcf5c66a8b1e1a7f1a2ff8e1f377107771 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Thu, 13 Nov 2025 01:50:25 +0800 Subject: [PATCH 07/10] there is a bug for map m_string_enum_map --- .../spi/ProtobufAccessorNamingStrategy.java | 81 +++++++++---------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java index fcd74159df..493b6269fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -259,28 +259,21 @@ && isMessageType( m.getReturnType() ) } ) ); - // repeated message field generates extra getXxxBuilderList() method - rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "BuilderList" ), - (method, methods) -> { - String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "BuilderList".length() ); - return methods.stream().anyMatch( m -> - getMethodName( m ).equals( withoutSuffix + "List" ) - && isListType( m.getReturnType() ) - ); - } - ) ); + return rules; + } - // repeated message field generates extra getXxxOrBuilderList() method + private List enumGetterSpecialMethodRules() { + List rules = new ArrayList<>(); + + // enum field generates extra 'int getXxxValue()' method rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "OrBuilderList" ), + method -> getMethodName( method ).endsWith( "Value" ), (method, methods) -> { String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "OrBuilderList".length() ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); return methods.stream().anyMatch( m -> - getMethodName( m ).equals( withoutSuffix + "List" ) - && isListType( m.getReturnType() ) + getMethodName( m ).equals( withoutSuffix ) + && isProtobufEnumType( m.getReturnType() ) ); } ) ); @@ -288,18 +281,18 @@ && isListType( m.getReturnType() ) return rules; } - private List enumGetterSpecialMethodRules() { + private List repeatedGetterSpecialMethodRules() { List rules = new ArrayList<>(); - // enum field generates extra 'int getXxxValue()' method + // repeated field generates 'getXxxCount()' method rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "Value" ), + method -> getMethodName( method ).endsWith( "Count" ), (method, methods) -> { String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "Value".length() ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); return methods.stream().anyMatch( m -> - getMethodName( m ).equals( withoutSuffix ) - && isProtobufEnumType( m.getReturnType() ) + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) ); } ) ); @@ -318,32 +311,25 @@ && isListType( m.getReturnType() ) } ) ); - // map field generates extra 'Map getXxxValueMap()' method + // repeated message field generates extra getXxxBuilderList() method rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "ValueMap" ) - && isMapType( method.getReturnType() ), + method -> getMethodName( method ).endsWith( "BuilderList" ), (method, methods) -> { String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "ValueMap".length() ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "BuilderList".length() ); return methods.stream().anyMatch( m -> - ( getMethodName( m ).equals( withoutSuffix + "Map" ) || getMethodName( m ).equals( withoutSuffix ) ) - && isMapType( m.getReturnType() ) + getMethodName( m ).equals( withoutSuffix + "List" ) + && isListType( m.getReturnType() ) ); } ) ); - return rules; - } - - private List repeatedGetterSpecialMethodRules() { - List rules = new ArrayList<>(); - - // repeated field generates 'getXxxCount()' method + // repeated message field generates extra getXxxOrBuilderList() method rules.add( new SpecialMethodRule( - method -> getMethodName( method ).endsWith( "Count" ), + method -> getMethodName( method ).endsWith( "OrBuilderList" ), (method, methods) -> { String methodName = getMethodName( method ); - String withoutSuffix = methodName.substring( 0, methodName.length() - "Count".length() ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "OrBuilderList".length() ); return methods.stream().anyMatch( m -> getMethodName( m ).equals( withoutSuffix + "List" ) && isListType( m.getReturnType() ) @@ -394,6 +380,20 @@ && isMapType( m.getReturnType() ) } ) ); + // map field generates extra 'Map getXxxValueMap()' method + rules.add( new SpecialMethodRule( + method -> getMethodName( method ).endsWith( "ValueMap" ) + && isMapType( method.getReturnType() ), + (method, methods) -> { + String methodName = getMethodName( method ); + String withoutSuffix = methodName.substring( 0, methodName.length() - "ValueMap".length() ); + return methods.stream().anyMatch( m -> + ( getMethodName( m ).equals( withoutSuffix + "Map" ) || getMethodName( m ).equals( withoutSuffix ) ) + && isMapType( m.getReturnType() ) + ); + } + ) ); + return rules; } @@ -782,11 +782,6 @@ public SpecialMethodRule( public boolean matches(ExecutableElement method, List allMethods) { return quickCheck.test( method ) && fullCheck.test( method, allMethods ); } - - public boolean matches(ExecutableElement method) { - List allMethods = getPublicNonStaticMethods( method.getEnclosingElement() ); - return quickCheck.test( method ) && fullCheck.test( method, allMethods ); - } } private static final class MethodSignature { From 4fa356ec0cfe1e399eca5927c8bf2618f662a97b Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Thu, 13 Nov 2025 22:35:17 +0800 Subject: [PATCH 08/10] fixes previous bug --- .../org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java | 1 - 1 file changed, 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java index 493b6269fb..992e1cca80 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -359,7 +359,6 @@ && isMapType( m.getReturnType() ) // map field generates deprecated 'getXxx()' and 'getMutableXxx()' methods rules.add( new SpecialMethodRule( method -> getMethodName( method ).startsWith( "get" ) - && !getMethodName( method ).endsWith( "Map" ) && isMapType( method.getReturnType() ), (method, methods) -> { String methodName = getMethodName( method ); From 998f32d0a3e2fa8797067e734ebb3b10cebe07ba Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Thu, 13 Nov 2025 23:05:56 +0800 Subject: [PATCH 09/10] Add more test case --- .../src/test/resources/protobufTest/pom.xml | 5 + .../mapstruct/itest/protobuf/Everything.java | 390 ++---------------- .../itest/protobuf/EverythingMapper.java | 117 +++++- .../main/proto/everything_edition2023.proto | 17 + .../src/main/proto/everything_proto2.proto | 17 + .../src/main/proto/everything_proto3.proto | 17 + .../itest/protobuf/ProtobufMapperTest.java | 69 +++- 7 files changed, 252 insertions(+), 380 deletions(-) diff --git a/integrationtest/src/test/resources/protobufTest/pom.xml b/integrationtest/src/test/resources/protobufTest/pom.xml index d87886a2bc..5e248ae658 100644 --- a/integrationtest/src/test/resources/protobufTest/pom.xml +++ b/integrationtest/src/test/resources/protobufTest/pom.xml @@ -29,6 +29,11 @@ + + org.projectlombok + lombok + compile + com.google.protobuf protobuf-java diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java index 3b35707644..4dbbfeacb8 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/Everything.java @@ -19,8 +19,11 @@ import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; import com.google.protobuf.StringValue; +import lombok.Data; +@Data public class Everything { // Primitive types private Integer int32; @@ -78,373 +81,36 @@ public class Everything { private Month month; // oneof - private int oneofInt32; - private String oneofString; + private Int32Value oneofInt32; + private StringValue oneofString; private Integer oneofEnum; private Message oneofMessage; - public Integer getInt32() { - return this.int32; - } - - public long getInt64() { - return this.int64; - } - - public Float getFloat_() { - return this.float_; - } - - public Double getDouble_() { - return this.double_; - } - - public Boolean getBool() { - return this.bool; - } - - public String getString() { - return this.string; - } - - public ByteString getBytes() { - return this.bytes; - } - - public Integer getInt32Value() { - return this.int32Value; - } - - public long getInt64Value() { - return this.int64Value; - } - - public FloatValue getFloatValue() { - return this.floatValue; - } - - public double getDoubleValue() { - return this.doubleValue; - } - - public boolean isBoolValue() { - return this.boolValue; - } - - public StringValue getStringValue() { - return this.stringValue; - } - - public ByteString getBytesValue() { - return this.bytesValue; - } - - public Integer[] getRepeatedInt32() { - return this.repeatedInt32; - } - - public Set getRepeatedInt64() { - return this.repeatedInt64; - } - - public List getRepeatedFloat() { - return this.repeatedFloat; - } - - public List getRepeatedDouble() { - return this.repeatedDouble; - } - - public List getRepeatedBool() { - return this.repeatedBool; - } - - public List getRepeatedString() { - return this.repeatedString; - } - - public List getRepeatedBytes() { - return this.repeatedBytes; - } - - public Map getMapInt32String() { - return this.mapInt32String; - } - - public Map getMapInt64String() { - return this.mapInt64String; - } - - public Map getMapBoolString() { - return this.mapBoolString; - } - - public Map getMapStringString() { - return this.mapStringString; - } - - public Map getMapStringBytes() { - return this.mapStringBytes; - } - - public Message getMessage() { - return this.message; - } - - public List getRepeatedMessage() { - return this.repeatedMessage; - } - - public Map getMapStringMessage() { - return this.mapStringMessage; - } - - public Integer getEnum_() { - return this.enum_; - } - - public int getOptionalEnum() { - return this.optionalEnum; - } - - public List getRepeatedEnum() { - return this.repeatedEnum; - } - - public Map getMapStringEnum() { - return this.mapStringEnum; - } - - public Instant getTimestamp() { - return this.timestamp; - } - - public Duration getDuration() { - return this.duration; - } - - public LocalTime getTimeOfDay() { - return this.timeOfDay; - } - - public String getDate() { - return this.date; - } - - public DayOfWeek getDayOfWeek() { - return this.dayOfWeek; - } - - public Month getMonth() { - return this.month; - } - - public void setInt32(Integer int32) { - this.int32 = int32; - } - - public void setInt64(long int64) { - this.int64 = int64; - } - - public void setFloat_(Float float_) { - this.float_ = float_; - } - - public void setDouble_(Double double_) { - this.double_ = double_; - } - - public void setBool(Boolean bool) { - this.bool = bool; - } - - public void setString(String string) { - this.string = string; - } - - public void setBytes(ByteString bytes) { - this.bytes = bytes; - } - - public void setInt32Value(Integer int32Value) { - this.int32Value = int32Value; - } - - public void setInt64Value(long int64Value) { - this.int64Value = int64Value; - } - - public void setFloatValue(FloatValue floatValue) { - this.floatValue = floatValue; - } - - public void setDoubleValue(double doubleValue) { - this.doubleValue = doubleValue; - } - - public void setBoolValue(boolean boolValue) { - this.boolValue = boolValue; - } - - public void setStringValue(StringValue stringValue) { - this.stringValue = stringValue; - } - - public void setBytesValue(ByteString bytesValue) { - this.bytesValue = bytesValue; - } - - public void setRepeatedInt32(Integer[] repeatedInt32) { - this.repeatedInt32 = repeatedInt32; - } - - public void setRepeatedInt64(Set repeatedInt64) { - this.repeatedInt64 = repeatedInt64; - } - - public void setRepeatedFloat(List repeatedFloat) { - this.repeatedFloat = repeatedFloat; - } - - public void setRepeatedDouble(List repeatedDouble) { - this.repeatedDouble = repeatedDouble; - } - - public void setRepeatedBool(List repeatedBool) { - this.repeatedBool = repeatedBool; - } - - public void setRepeatedString(List repeatedString) { - this.repeatedString = repeatedString; - } - - public void setRepeatedBytes(List repeatedBytes) { - this.repeatedBytes = repeatedBytes; - } - - public void setMapInt32String(Map mapInt32String) { - this.mapInt32String = mapInt32String; - } - - public void setMapInt64String(Map mapInt64String) { - this.mapInt64String = mapInt64String; - } - - public void setMapBoolString(Map mapBoolString) { - this.mapBoolString = mapBoolString; - } - - public void setMapStringString(Map mapStringString) { - this.mapStringString = mapStringString; - } - - public void setMapStringBytes(Map mapStringBytes) { - this.mapStringBytes = mapStringBytes; - } - - public void setMessage(Message message) { - this.message = message; - } - - public void setRepeatedMessage(List repeatedMessage) { - this.repeatedMessage = repeatedMessage; - } - - public void setMapStringMessage(Map mapStringMessage) { - this.mapStringMessage = mapStringMessage; - } - - public void setEnum_(Integer enum_) { - this.enum_ = enum_; - } - - public void setOptionalEnum(int optionalEnum) { - this.optionalEnum = optionalEnum; - } - - public void setRepeatedEnum(List repeatedEnum) { - this.repeatedEnum = repeatedEnum; - } - - public void setMapStringEnum(Map mapStringEnum) { - this.mapStringEnum = mapStringEnum; - } - - public void setTimestamp(Instant timestamp) { - this.timestamp = timestamp; - } - - public void setDuration(Duration duration) { - this.duration = duration; - } - - public void setTimeOfDay(LocalTime timeOfDay) { - this.timeOfDay = timeOfDay; - } - - public void setDate(String date) { - this.date = date; - } - - public void setDayOfWeek(DayOfWeek dayOfWeek) { - this.dayOfWeek = dayOfWeek; - } - - public void setMonth(Month month) { - this.month = month; - } - - public int getOneofInt32() { - return oneofInt32; - } - - public void setOneofInt32(int oneofInt32) { - this.oneofInt32 = oneofInt32; - } - - public String getOneofString() { - return oneofString; - } - - public void setOneofString(String oneofString) { - this.oneofString = oneofString; - } - - public Integer getOneofEnum() { - return oneofEnum; - } - - public void setOneofEnum(Integer oneofEnum) { - this.oneofEnum = oneofEnum; - } - - public Message getOneofMessage() { - return oneofMessage; - } - - public void setOneofMessage(Message oneofMessage) { - this.oneofMessage = oneofMessage; - } - + // deprecated field + @Deprecated + private Integer deprecatedInt32; + @Deprecated + private String deprecatedString; + @Deprecated + private List deprecatedRepeatedString; + @Deprecated + private Map deprecatedMapStringInt32; + @Deprecated + private Integer deprecatedEnum; + + // special naming + private String strBytes; + private Integer enValue; + private List reStringList; + private List reEnumValueList; + private Map maStringStringMap; + private Map maStringEnumMap; + private Message msgBuilder; + private Message msgOrBuilder; + + @Data public static class Message { private long id; private String name; - - public long getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public void setId(long id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } } } diff --git a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java index e79fd4c878..2a70bb8ae0 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java +++ b/integrationtest/src/test/resources/protobufTest/src/main/java/org/mapstruct/itest/protobuf/EverythingMapper.java @@ -7,47 +7,146 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; import org.mapstruct.itest.protobuf.Everything; +import static org.mapstruct.CollectionMappingStrategy.TARGET_IMMUTABLE; import static org.mapstruct.NullValueCheckStrategy.ALWAYS; +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; import static org.mapstruct.ReportingPolicy.ERROR; -@Mapper(nullValueCheckStrategy = ALWAYS, unmappedTargetPolicy = ERROR, unmappedSourcePolicy = ERROR) -public interface EverythingMapper { +@Mapper( + nullValueCheckStrategy = ALWAYS, + nullValuePropertyMappingStrategy = IGNORE, + collectionMappingStrategy = TARGET_IMMUTABLE, + unmappedTargetPolicy = ERROR, + unmappedSourcePolicy = ERROR) +public abstract class EverythingMapper { - EverythingMapper INSTANCE = Mappers.getMapper( EverythingMapper.class ); + public static final EverythingMapper INSTANCE = Mappers.getMapper( EverythingMapper.class ); // proto2 @Mapping(target = "float_", source = "float") @Mapping(target = "double_", source = "double") @Mapping(target = "enum_", source = "enum") - Everything proto2ToJavaBean(EverythingProto2 proto); + public abstract Everything proto2ToJavaBean(EverythingProto2 proto); @Mapping(target = "float", source = "float_") @Mapping(target = "double", source = "double_") @Mapping(target = "enum", source = "enum_") - EverythingProto2 javaBeanToProto2(Everything javaBean); + public abstract EverythingProto2 javaBeanToProto2(Everything javaBean); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + public abstract void updateProto2FromJavaBean(@MappingTarget EverythingProto2.Builder proto, Everything javaBean); + + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + public abstract void updateJavaBeanFromProto2(@MappingTarget Everything javaBean, EverythingProto2 proto); // proto3 @Mapping(target = "float_", source = "float") @Mapping(target = "double_", source = "double") @Mapping(target = "enum_", source = "enum") - Everything proto3ToJavaBean(EverythingProto3 proto); + public abstract Everything proto3ToJavaBean(EverythingProto3 proto); @Mapping(target = "float", source = "float_") @Mapping(target = "double", source = "double_") @Mapping(target = "enum", source = "enum_") - EverythingProto3 javaBeanToProto3(Everything javaBean); + public abstract EverythingProto3 javaBeanToProto3(Everything javaBean); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + public abstract void updateProto3FromJavaBean(@MappingTarget EverythingProto3.Builder proto, Everything javaBean); + + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + public abstract void updateJavaBeanFromProto3(@MappingTarget Everything javaBean, EverythingProto3 proto); // edition 2023 @Mapping(target = "float_", source = "float") @Mapping(target = "double_", source = "double") @Mapping(target = "enum_", source = "enum") - Everything edition2023ToJavaBean(EverythingEdition2023 proto); + public abstract Everything edition2023ToJavaBean(EverythingEdition2023 proto); + + @Mapping(target = "float", source = "float_") + @Mapping(target = "double", source = "double_") + @Mapping(target = "enum", source = "enum_") + public abstract EverythingEdition2023 javaBeanToEdition2023(Everything javaBean); @Mapping(target = "float", source = "float_") @Mapping(target = "double", source = "double_") @Mapping(target = "enum", source = "enum_") - EverythingEdition2023 javaBeanToEdition2023(Everything javaBean); + public abstract void updateEdition2023FromJavaBean( + @MappingTarget EverythingEdition2023.Builder proto, Everything javaBean); + + @Mapping(target = "float_", source = "float") + @Mapping(target = "double_", source = "double") + @Mapping(target = "enum_", source = "enum") + public abstract void updateJavaBeanFromEdition2023(@MappingTarget Everything javaBean, EverythingEdition2023 proto); + + protected EverythingProto2.Message javaBeanMessageToProto2Message(Everything.Message value) { + if ( value == null ) { + return null; + } + var builder = EverythingProto2.Message.newBuilder(); + builder.setId( value.getId() ); + builder.setName( value.getName() ); + return builder.build(); + } + + protected Everything.Message proto2MessageToJavaBeanMessage(EverythingProto2.Message value) { + if ( value == null ) { + return null; + } + Everything.Message message = new Everything.Message(); + message.setId( value.getId() ); + message.setName( value.getName() ); + return message; + } + + protected EverythingProto3.Message javaBeanMessageToProto3Message(Everything.Message value) { + if ( value == null ) { + return null; + } + var builder = EverythingProto3.Message.newBuilder(); + builder.setId( value.getId() ); + builder.setName( value.getName() ); + return builder.build(); + } + + protected Everything.Message proto3MessageToJavaBeanMessage(EverythingProto3.Message value) { + if ( value == null ) { + return null; + } + Everything.Message message = new Everything.Message(); + message.setId( value.getId() ); + message.setName( value.getName() ); + return message; + } + + protected EverythingEdition2023.Message javaBeanMessageToEdition2023Message(Everything.Message value) { + if ( value == null ) { + return null; + } + var builder = EverythingEdition2023.Message.newBuilder(); + builder.setId( value.getId() ); + builder.setName( value.getName() ); + return builder.build(); + } + + protected Everything.Message edition2023MessageToJavaBeanMessage(EverythingEdition2023.Message value) { + if ( value == null ) { + return null; + } + Everything.Message message = new Everything.Message(); + message.setId( value.getId() ); + message.setName( value.getName() ); + return message; + } } diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto index 4a0b3dd19d..12bc399dbe 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_edition2023.proto @@ -77,6 +77,23 @@ message EverythingEdition2023 { Message oneof_message = 83; } + // deprecated field + int32 deprecated_int32 = 90 [deprecated = true]; + string deprecated_string = 91 [deprecated = true]; + repeated string deprecated_repeated_string = 92 [deprecated = true]; + map deprecated_map_string_int32 = 93 [deprecated = true]; + Enum deprecated_enum = 94 [deprecated = true]; + + // special naming + string str_bytes = 100; + Enum en_value = 101; + repeated string re_string_list = 102; + repeated Enum re_enum_value_list = 107; + map ma_string_string_map = 103; + map ma_string_enum_map = 104; + Message msg_builder = 105; + Message msg_or_builder = 106; + message Message { int64 id = 1; string name = 2; diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto index 57b62e4a3e..9fc93c8d19 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto2.proto @@ -77,6 +77,23 @@ message EverythingProto2 { Message oneof_message = 83; } + // deprecated field + required int32 deprecated_int32 = 90 [deprecated = true]; + required string deprecated_string = 91 [deprecated = true]; + repeated string deprecated_repeated_string = 92 [deprecated = true]; + map deprecated_map_string_int32 = 93 [deprecated = true]; + required Enum deprecated_enum = 94 [deprecated = true]; + + // special naming + required string str_bytes = 100; + required Enum en_value = 101; + repeated string re_string_list = 102; + repeated Enum re_enum_value_list = 107; + map ma_string_string_map = 103; + map ma_string_enum_map = 104; + required Message msg_builder = 105; + required Message msg_or_builder = 106; + message Message { optional int64 id = 1; optional string name = 2; diff --git a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto index 56745e923e..f2d058c1c3 100644 --- a/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto +++ b/integrationtest/src/test/resources/protobufTest/src/main/proto/everything_proto3.proto @@ -77,6 +77,23 @@ message EverythingProto3 { Message oneof_message = 83; } + // deprecated field + int32 deprecated_int32 = 90 [deprecated = true]; + string deprecated_string = 91 [deprecated = true]; + repeated string deprecated_repeated_string = 92 [deprecated = true]; + map deprecated_map_string_int32 = 93 [deprecated = true]; + Enum deprecated_enum = 94 [deprecated = true]; + + // special naming + string str_bytes = 100; + Enum en_value = 101; + repeated string re_string_list = 102; + repeated Enum re_enum_value_list = 107; + map ma_string_string_map = 103; + map ma_string_enum_map = 104; + Message msg_builder = 105; + Message msg_or_builder = 106; + message Message { int64 id = 1; string name = 2; diff --git a/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java index c88736d7f4..a17647fed8 100644 --- a/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java +++ b/integrationtest/src/test/resources/protobufTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -23,6 +23,7 @@ import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; import com.google.protobuf.StringValue; import org.junit.Test; import org.mapstruct.itest.protobuf.EverythingMapper; @@ -53,7 +54,11 @@ public void testSimpleImmutableBuilderHappyPath() { @Test public void testLombokToImmutable() { - PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); + PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( + "Bob", + 33, + new AddressDto( "Wild Drive" ) + ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); @@ -170,21 +175,67 @@ public void testProtobufConvert() { original.setMonth( Month.JANUARY ); // Oneof - original.setOneofInt32( 12345 ); + original.setOneofInt32( Int32Value.of( 12345 ) ); + + // Deprecated + original.setDeprecatedInt32( 111 ); + original.setDeprecatedString( "deprecated" ); + original.setDeprecatedRepeatedString( Arrays.asList( "dep1", "dep2" ) ); + Map deprecatedMap = new HashMap<>(); + deprecatedMap.put( "depKey1", 1 ); + deprecatedMap.put( "depKey2", 2 ); + original.setDeprecatedMapStringInt32( deprecatedMap ); + original.setDeprecatedEnum( 1 ); + + // Special naming + original.setStrBytes( "string bytes" ); + original.setEnValue( 1 ); + original.setReStringList( Arrays.asList( "reStr1", "reStr2" ) ); + original.setReEnumValueList( Arrays.asList( 1 ) ); + Map maStringStringMap = new HashMap<>(); + maStringStringMap.put( "mapStrKey1", "mapStrValue1" ); + original.setMaStringStringMap( maStringStringMap ); + Map maStringEnumMap = new HashMap<>(); + maStringEnumMap.put( "mapEnumKey1", "ENUM_VALUE_1" ); + original.setMaStringEnumMap( maStringEnumMap ); + Everything.Message msgBuilder = new Everything.Message(); + msgBuilder.setId( 555L ); + msgBuilder.setName( "msg builder" ); + original.setMsgBuilder( msgBuilder ); + Everything.Message msgOrBuilder = new Everything.Message(); + msgOrBuilder.setId( 666L ); + msgOrBuilder.setName( "msg or builder" ); + original.setMsgOrBuilder( msgOrBuilder ); + + // Convert to Proto3, Proto2, and Edition2023 and back + EverythingProto3 proto3Message = EverythingMapper.INSTANCE.javaBeanToProto3( original ); + Everything proto3Result = EverythingMapper.INSTANCE.proto3ToJavaBean( proto3Message ); - // Convert EverythingProto2 proto2Message = EverythingMapper.INSTANCE.javaBeanToProto2( original ); Everything proto2Result = EverythingMapper.INSTANCE.proto2ToJavaBean( proto2Message ); - EverythingProto3 proto3Message = EverythingMapper.INSTANCE.javaBeanToProto3( original ); - Everything proto3Result = EverythingMapper.INSTANCE.proto3ToJavaBean( proto3Message ); - EverythingEdition2023 edition2023Message = EverythingMapper.INSTANCE.javaBeanToEdition2023( original ); Everything edition2023Result = EverythingMapper.INSTANCE.edition2023ToJavaBean( edition2023Message ); + // Update existing Proto messages from Java bean and back + EverythingProto2.Builder proto2Builder = EverythingProto2.newBuilder(); + EverythingMapper.INSTANCE.updateProto2FromJavaBean( proto2Builder, original ); + Everything proto2Updated = EverythingMapper.INSTANCE.proto2ToJavaBean( proto2Builder.build() ); + + EverythingProto3.Builder proto3Builder = EverythingProto3.newBuilder(); + EverythingMapper.INSTANCE.updateProto3FromJavaBean( proto3Builder, original ); + Everything proto3Updated = EverythingMapper.INSTANCE.proto3ToJavaBean( proto3Builder.build() ); + + EverythingEdition2023.Builder edition2023Builder = EverythingEdition2023.newBuilder(); + EverythingMapper.INSTANCE.updateEdition2023FromJavaBean( edition2023Builder, original ); + Everything edition2023Updated = EverythingMapper.INSTANCE.edition2023ToJavaBean( edition2023Builder.build() ); + // Use AssertJ recursive comparison to verify equality - assertThat( proto3Result ).usingRecursiveComparison().isEqualTo( original ); - assertThat( proto2Result ).usingRecursiveComparison().isEqualTo( original ); - assertThat( edition2023Result ).usingRecursiveComparison().isEqualTo( original ); + assertThat( proto3Result ).isEqualTo( original ); + assertThat( proto2Result ).isEqualTo( original ); + assertThat( edition2023Result ).isEqualTo( original ); + assertThat( proto2Updated ).isEqualTo( original ); + assertThat( proto3Updated ).isEqualTo( original ); + assertThat( edition2023Updated ).isEqualTo( original ); } } From 08a8e7d735eb5c7636248b06a69feab3214a8fc4 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Fri, 14 Nov 2025 00:09:46 +0800 Subject: [PATCH 10/10] optimize isRepeatedGetter --- .../spi/ProtobufAccessorNamingStrategy.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java index 992e1cca80..7d1dee6819 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ProtobufAccessorNamingStrategy.java @@ -589,9 +589,20 @@ private boolean isEnumType(TypeMirror t) { private boolean isRepeatedGetter(ExecutableElement element) { // repeated fields getter: getXxxList() - return hasPrefixWithUpperCaseNext( element, "get" ) + if ( hasPrefixWithUpperCaseNext( element, "get" ) && getMethodName( element ).endsWith( "List" ) - && isListType( element.getReturnType() ); + && isListType( element.getReturnType() ) ) { + List allMethods = getPublicNonStaticMethods( element.getEnclosingElement() ); + for ( SpecialMethodRule rule : repeatedGetterSpecialMethodRules() ) { + if ( rule.matches( element, allMethods ) ) { + return false; + } + } + return true; + } + else { + return false; + } } private boolean isMapGetter(ExecutableElement element) { @@ -610,8 +621,9 @@ && isMapType( element.getReturnType() ) ) { } return true; } - - return false; + else { + return false; + } } private static String getMethodName(ExecutableElement element) {